1 /******************************************************************************
2 * $Id$
3 *
4 * Project: MapServer
5 * Purpose: Legend generation.
6 * Author: Steve Lime and the MapServer team.
7 *
8 ******************************************************************************
9 * Copyright (c) 1996-2005 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
33
34 #define PSF .8
35 #define VMARGIN 5 /* margin at top and bottom of legend graphic */
36 #define HMARGIN 5 /* margin at left and right of legend graphic */
37
38
msDrawGradientSymbol(rendererVTableObj * renderer,imageObj * image_draw,double x_center,double y_center,int width,int height,styleObj * style)39 static int msDrawGradientSymbol(rendererVTableObj* renderer,
40 imageObj* image_draw,
41 double x_center,
42 double y_center,
43 int width,
44 int height,
45 styleObj* style)
46 {
47 int i, j;
48 unsigned char *r,*g,*b,*a;
49 symbolObj symbol;
50 rasterBufferObj* rb;
51 symbolStyleObj symbolStyle;
52 int ret;
53
54 initSymbol(&symbol);
55 rb = (rasterBufferObj*)calloc(1,sizeof(rasterBufferObj));
56 symbol.pixmap_buffer = rb;
57 rb->type = MS_BUFFER_BYTE_RGBA;
58 rb->width = width;
59 rb->height = height;
60 rb->data.rgba.row_step = rb->width * 4;
61 rb->data.rgba.pixel_step = 4;
62 rb->data.rgba.pixels = (unsigned char*)malloc(
63 rb->width*rb->height*4*sizeof(unsigned char));
64 b = rb->data.rgba.b = &rb->data.rgba.pixels[0];
65 g = rb->data.rgba.g = &rb->data.rgba.pixels[1];
66 r = rb->data.rgba.r = &rb->data.rgba.pixels[2];
67 a = rb->data.rgba.a = &rb->data.rgba.pixels[3];
68 for( j = 0; j < rb->height; j++ )
69 {
70 for( i = 0; i < rb->width; i++ )
71 {
72 msValueToRange(style, style->minvalue +
73 (double)i / rb->width * (style->maxvalue - style->minvalue), MS_COLORSPACE_RGB);
74 b[4*(j * rb->width + i)] = style->color.blue;
75 g[4*(j * rb->width + i)] = style->color.green;
76 r[4*(j * rb->width + i)] = style->color.red;
77 a[4*(j * rb->width + i)] = style->color.alpha;
78 }
79 }
80 INIT_SYMBOL_STYLE(symbolStyle);
81 ret = renderer->renderPixmapSymbol(image_draw, x_center, y_center, &symbol, &symbolStyle);
82 msFreeSymbol(&symbol);
83 return ret;
84 }
85
86 /*
87 * generic function for drawing a legend icon. (added for bug #2348)
88 * renderer specific drawing functions shouldn't be called directly, but through
89 * this function
90 */
msDrawLegendIcon(mapObj * map,layerObj * lp,classObj * theclass,int width,int height,imageObj * image,int dstX,int dstY,int scale_independant,class_hittest * hittest)91 int msDrawLegendIcon(mapObj *map, layerObj *lp, classObj *theclass,
92 int width, int height, imageObj *image, int dstX, int dstY,
93 int scale_independant, class_hittest *hittest)
94 {
95 int i, type, hasmarkersymbol, ret=MS_SUCCESS;
96 double offset;
97 double polygon_contraction = 0.5; /* used to account for the width of a polygon's outline */
98 shapeObj box, zigzag;
99 lineObj box_line,zigzag_line;
100 pointObj box_point[5], zigzag_point[4];
101 pointObj marker;
102 char szPath[MS_MAXPATHLEN];
103 styleObj outline_style;
104 imageObj *image_draw = image;
105 rendererVTableObj *renderer;
106 outputFormatObj *transFormat = NULL, *altFormat=NULL;
107 const char *alternativeFormatString = NULL;
108
109 if(!MS_RENDERER_PLUGIN(image->format)) {
110 msSetError(MS_MISCERR,"unsupported image format","msDrawLegendIcon()");
111 return MS_FAILURE;
112 }
113
114 alternativeFormatString = msLayerGetProcessingKey(lp, "RENDERER");
115 if (MS_RENDERER_PLUGIN(image_draw->format) && alternativeFormatString!=NULL &&
116 (altFormat= msSelectOutputFormat(map, alternativeFormatString))) {
117 msInitializeRendererVTable(altFormat);
118
119 image_draw = msImageCreate(image->width, image->height,
120 altFormat, image->imagepath, image->imageurl, map->resolution, map->defresolution, &map->imagecolor);
121 image_draw->map = map;
122 renderer = MS_IMAGE_RENDERER(image_draw);
123 } else {
124 renderer = MS_IMAGE_RENDERER(image_draw);
125 if (lp->compositer && renderer->compositeRasterBuffer) {
126 image_draw = msImageCreate(image->width, image->height,
127 image->format, image->imagepath, image->imageurl, map->resolution, map->defresolution, NULL);
128 if (!image_draw) {
129 msSetError(MS_MISCERR, "Unable to initialize temporary transparent image.",
130 "msDrawLegendIcon()");
131 return (MS_FAILURE);
132 }
133 image_draw->map = map;
134 }
135 }
136
137
138 if(renderer->supports_clipping && MS_VALID_COLOR(map->legend.outlinecolor)) {
139 /* keep GD specific code here for now as it supports clipping */
140 rectObj clip;
141 clip.maxx = dstX + width - 1;
142 clip.maxy = dstY + height -1;
143 clip.minx = dstX;
144 clip.miny = dstY;
145 renderer->setClip(image_draw,clip);
146 }
147
148 /* if the class has a keyimage, treat it as a point layer
149 * (the keyimage will be treated there) */
150 if(theclass->keyimage != NULL) {
151 type = MS_LAYER_POINT;
152 } else {
153 /* some polygon layers may be better drawn using zigzag if there is no fill */
154 type = lp->type;
155 if(type == MS_LAYER_POLYGON) {
156 type = MS_LAYER_LINE;
157 for(i=0; i<theclass->numstyles; i++) {
158 if(MS_VALID_COLOR(theclass->styles[i]->color)) { /* there is a fill */
159 type = MS_LAYER_POLYGON;
160 }
161 if(MS_VALID_COLOR(theclass->styles[i]->outlinecolor)) { /* there is an outline */
162 polygon_contraction = MS_MAX(polygon_contraction, theclass->styles[i]->width / 2.0);
163 }
164 }
165 }
166 }
167
168 /* initialize the box used for polygons and for outlines */
169 msInitShape(&box);
170 box.line = &box_line;
171 box.numlines = 1;
172 box.line[0].point = box_point;
173 box.line[0].numpoints = 5;
174 box.type = MS_SHAPE_POLYGON;
175
176 box.line[0].point[0].x = dstX + polygon_contraction;
177 box.line[0].point[0].y = dstY + polygon_contraction;
178 box.line[0].point[1].x = dstX + width - polygon_contraction;
179 box.line[0].point[1].y = dstY + polygon_contraction;
180 box.line[0].point[2].x = dstX + width - polygon_contraction;
181 box.line[0].point[2].y = dstY + height - polygon_contraction;
182 box.line[0].point[3].x = dstX + polygon_contraction;
183 box.line[0].point[3].y = dstY + height - polygon_contraction;
184 box.line[0].point[4].x = box.line[0].point[0].x;
185 box.line[0].point[4].y = box.line[0].point[0].y;
186
187
188
189 /*
190 ** now draw the appropriate color/symbol/size combination
191 */
192
193 /* Scalefactor will be infinity when SIZEUNITS is set in LAYER */
194 if(lp->sizeunits != MS_PIXELS) {
195 lp->scalefactor = 1.0;
196 }
197
198 switch(type) {
199 case MS_LAYER_POINT:
200 marker.x = dstX + MS_NINT(width / 2.0);
201 marker.y = dstY + MS_NINT(height / 2.0);
202 if(theclass->keyimage != NULL) {
203 int symbolNum;
204 styleObj imgStyle;
205 symbolObj *symbol=NULL;
206 symbolNum = msAddImageSymbol(&(map->symbolset), msBuildPath(szPath, map->mappath, theclass->keyimage));
207 if(symbolNum == -1) {
208 msSetError(MS_IMGERR, "Failed to open legend key image", "msCreateLegendIcon()");
209 return(MS_FAILURE);
210 }
211
212 symbol = map->symbolset.symbol[symbolNum];
213
214 initStyle(&imgStyle);
215 /*set size so that symbol will be scaled properly #3296*/
216 if (width/symbol->sizex < height/symbol->sizey)
217 imgStyle.size = symbol->sizey*(width/symbol->sizex);
218 else
219 imgStyle.size = symbol->sizey*(height/symbol->sizey);
220
221 if (imgStyle.size > imgStyle.maxsize)
222 imgStyle.maxsize = imgStyle.size;
223
224 imgStyle.symbol = symbolNum;
225 ret = msDrawMarkerSymbol(map ,image_draw,&marker,&imgStyle, 1.0);
226 if(UNLIKELY(ret == MS_FAILURE)) goto legend_icon_cleanup;
227 /* TO DO: we may want to handle this differently depending on the relative size of the keyimage */
228 } else {
229 for(i=0; i<theclass->numstyles; i++) {
230 if(!scale_independant && map->scaledenom > 0) {
231 styleObj *lp = theclass->styles[i];
232 if((lp->maxscaledenom > 0) && (map->scaledenom > lp->maxscaledenom)) continue;
233 if((lp->minscaledenom > 0) && (map->scaledenom <= lp->minscaledenom)) continue;
234 }
235 if(hittest && hittest->stylehits[i].status == 0) continue;
236 ret = msDrawMarkerSymbol(map, image_draw, &marker, theclass->styles[i], lp->scalefactor * image->resolutionfactor);
237 if(UNLIKELY(ret == MS_FAILURE)) goto legend_icon_cleanup;
238 }
239 }
240 break;
241 case MS_LAYER_LINE:
242 offset = 1;
243 /* To set the offset, we only check the size/width parameter of the first style */
244 if (theclass->numstyles > 0) {
245 if (theclass->styles[0]->symbol > 0 && theclass->styles[0]->symbol < map->symbolset.numsymbols &&
246 map->symbolset.symbol[theclass->styles[0]->symbol]->type != MS_SYMBOL_SIMPLE)
247 offset = theclass->styles[0]->size/2;
248 else
249 offset = theclass->styles[0]->width/2;
250 }
251 msInitShape(&zigzag);
252 zigzag.line = &zigzag_line;
253 zigzag.numlines = 1;
254 zigzag.line[0].point = zigzag_point;
255 zigzag.line[0].numpoints = 4;
256 zigzag.type = MS_SHAPE_LINE;
257
258 zigzag.line[0].point[0].x = dstX + offset;
259 zigzag.line[0].point[0].y = dstY + height - offset;
260 zigzag.line[0].point[1].x = dstX + MS_NINT(width / 3.0) - 1;
261 zigzag.line[0].point[1].y = dstY + offset;
262 zigzag.line[0].point[2].x = dstX + MS_NINT(2.0 * width / 3.0) - 1;
263 zigzag.line[0].point[2].y = dstY + height - offset;
264 zigzag.line[0].point[3].x = dstX + width - offset;
265 zigzag.line[0].point[3].y = dstY + offset;
266
267 for(i=0; i<theclass->numstyles; i++) {
268 if(!scale_independant && map->scaledenom > 0) {
269 styleObj *lp = theclass->styles[i];
270 if((lp->maxscaledenom > 0) && (map->scaledenom > lp->maxscaledenom)) continue;
271 if((lp->minscaledenom > 0) && (map->scaledenom <= lp->minscaledenom)) continue;
272 }
273 if(hittest && hittest->stylehits[i].status == 0) continue;
274 if (theclass->styles[i]->_geomtransform.type == MS_GEOMTRANSFORM_NONE ||
275 theclass->styles[i]->_geomtransform.type == MS_GEOMTRANSFORM_LABELPOINT ||
276 theclass->styles[i]->_geomtransform.type == MS_GEOMTRANSFORM_LABELPOLY) {
277 if (theclass->styles[i]->outlinewidth > 0) {
278 /* Swap the style contents to render the outline first,
279 * and then restore the style to render the interior of the line
280 */
281 msOutlineRenderingPrepareStyle(theclass->styles[i], map, lp, image);
282 ret = msDrawLineSymbol(map, image_draw, &zigzag, theclass->styles[i], lp->scalefactor * image_draw->resolutionfactor);
283 msOutlineRenderingRestoreStyle(theclass->styles[i], map, lp, image);
284 if(UNLIKELY(ret == MS_FAILURE)) goto legend_icon_cleanup;
285 }
286 ret = msDrawLineSymbol(map, image_draw, &zigzag, theclass->styles[i], lp->scalefactor * image_draw->resolutionfactor);
287 if(UNLIKELY(ret == MS_FAILURE)) goto legend_icon_cleanup;
288 }
289 else {
290 if (theclass->styles[i]->outlinewidth > 0) {
291 /* Swap the style contents to render the outline first,
292 * and then restore the style to render the interior of the line
293 */
294 msOutlineRenderingPrepareStyle(theclass->styles[i], map, lp, image);
295 ret = msDrawTransformedShape(map, image_draw, &zigzag, theclass->styles[i], lp->scalefactor * image_draw->resolutionfactor);
296 msOutlineRenderingRestoreStyle(theclass->styles[i], map, lp, image);
297 if(UNLIKELY(ret == MS_FAILURE)) goto legend_icon_cleanup;
298 }
299 ret = msDrawTransformedShape(map, image_draw, &zigzag, theclass->styles[i], lp->scalefactor * image_draw->resolutionfactor);
300 if(UNLIKELY(ret == MS_FAILURE)) goto legend_icon_cleanup;
301 }
302 }
303
304 break;
305 case MS_LAYER_CIRCLE:
306 case MS_LAYER_RASTER:
307 case MS_LAYER_CHART:
308 case MS_LAYER_POLYGON:
309 for(i=0; i<theclass->numstyles; i++) {
310 if(!scale_independant && map->scaledenom > 0) {
311 styleObj *lp = theclass->styles[i];
312 if((lp->maxscaledenom > 0) && (map->scaledenom > lp->maxscaledenom)) continue;
313 if((lp->minscaledenom > 0) && (map->scaledenom <= lp->minscaledenom)) continue;
314 }
315 if(hittest && hittest->stylehits[i].status == 0) continue;
316 if (theclass->styles[i]->_geomtransform.type == MS_GEOMTRANSFORM_NONE ||
317 theclass->styles[i]->_geomtransform.type == MS_GEOMTRANSFORM_LABELPOINT ||
318 theclass->styles[i]->_geomtransform.type == MS_GEOMTRANSFORM_LABELPOLY) {
319
320 if (MS_VALID_COLOR(theclass->styles[i]->mincolor))
321 {
322 ret = msDrawGradientSymbol(renderer,
323 image_draw,
324 dstX + width / 2.,
325 dstY + height / 2.0,
326 width - 2 * polygon_contraction,
327 height - 2 * polygon_contraction,
328 theclass->styles[i]);
329 }
330 else
331 {
332 ret = msDrawShadeSymbol(map, image_draw, &box, theclass->styles[i], lp->scalefactor * image_draw->resolutionfactor);
333 }
334 if(UNLIKELY(ret == MS_FAILURE)) goto legend_icon_cleanup;
335 }
336 else {
337 ret = msDrawTransformedShape(map, image_draw, &box,
338 theclass->styles[i], lp->scalefactor);
339 if(UNLIKELY(ret == MS_FAILURE)) goto legend_icon_cleanup;
340 }
341 }
342 break;
343 default:
344 return MS_FAILURE;
345 break;
346 } /* end symbol drawing */
347
348 /* handle label styles */
349 for(i=0; i<theclass->numlabels; i++) {
350 labelObj *l = theclass->labels[i];
351 if(!scale_independant && map->scaledenom > 0) {
352 if(msScaleInBounds(map->scaledenom, l->minscaledenom, l->maxscaledenom)) {
353 int j;
354 for(j=0; j<l->numstyles; j++) {
355 styleObj *s = l->styles[j];
356 marker.x = dstX + MS_NINT(width / 2.0);
357 marker.y = dstY + MS_NINT(height / 2.0);
358 if(s->_geomtransform.type == MS_GEOMTRANSFORM_LABELPOINT) {
359 ret = msDrawMarkerSymbol(map, image_draw, &marker, s, lp->scalefactor);
360 if(UNLIKELY(ret == MS_FAILURE)) goto legend_icon_cleanup;
361 }
362 }
363 }
364 }
365 }
366
367 /* handle "pure" text layers, i.e. layers with no symbology */
368 hasmarkersymbol = 0;
369 if(theclass->numstyles == 0) {
370 for(i=0; i<theclass->numlabels; i++) {
371 labelObj *l = theclass->labels[i];
372 if(!scale_independant && map->scaledenom > 0) {
373 if(msScaleInBounds(map->scaledenom, l->minscaledenom, l->maxscaledenom)) {
374 int j;
375 for(j=0; j<l->numstyles; j++) {
376 styleObj *s = l->styles[j];
377 if(s->_geomtransform.type == MS_GEOMTRANSFORM_LABELPOINT) {
378 hasmarkersymbol = 1;
379 }
380 }
381 }
382 }
383 }
384 } else {
385 hasmarkersymbol = 1;
386 }
387
388 if(!hasmarkersymbol && theclass->numlabels>0) {
389 textSymbolObj ts;
390 pointObj textstartpt;
391 marker.x = dstX + MS_NINT(width / 2.0);
392 marker.y = dstY + MS_NINT(height / 2.0);
393 initTextSymbol(&ts);
394 msPopulateTextSymbolForLabelAndString(&ts,theclass->labels[0],msStrdup("Az"),lp->scalefactor*image_draw->resolutionfactor,image_draw->resolutionfactor, duplicate_always);
395 ts.label->size = height - 1;
396 ts.rotation = 0;
397 ret = msComputeTextPath(map,&ts);
398 if(UNLIKELY(ret == MS_FAILURE)) goto legend_icon_cleanup;
399 textstartpt = get_metrics(&marker,MS_CC,ts.textpath,0,0,0,0,NULL);
400 ret = msDrawTextSymbol(map,image_draw, textstartpt, &ts);
401 freeTextSymbol(&ts);
402 if(UNLIKELY(ret == MS_FAILURE)) goto legend_icon_cleanup;
403
404 }
405
406
407 /* handle an outline if necessary */
408 if(MS_VALID_COLOR(map->legend.outlinecolor)) {
409 initStyle(&outline_style);
410 outline_style.color = map->legend.outlinecolor;
411 ret = msDrawLineSymbol(map, image_draw, &box, &outline_style, image_draw->resolutionfactor);
412 if(UNLIKELY(ret == MS_FAILURE)) goto legend_icon_cleanup;
413 /* reset clipping rectangle */
414 if(renderer->supports_clipping)
415 renderer->resetClip(image_draw);
416 }
417
418 if (altFormat) {
419 rendererVTableObj *renderer = MS_IMAGE_RENDERER(image);
420 rendererVTableObj *altrenderer = MS_IMAGE_RENDERER(image_draw);
421 rasterBufferObj rb;
422 memset(&rb,0,sizeof(rasterBufferObj));
423
424 ret = altrenderer->getRasterBufferHandle(image_draw,&rb);
425 if(UNLIKELY(ret == MS_FAILURE)) goto legend_icon_cleanup;
426 ret = renderer->mergeRasterBuffer(image,&rb,((lp->compositer)?lp->compositer->opacity*0.01:1.0),0,0,0,0,rb.width,rb.height);
427 if(UNLIKELY(ret == MS_FAILURE)) goto legend_icon_cleanup;
428 /*
429 * hack to work around bug #3834: if we have use an alternate renderer, the symbolset may contain
430 * symbols that reference it. We want to remove those references before the altFormat is destroyed
431 * to avoid a segfault and/or a leak, and so the the main renderer doesn't pick the cache up thinking
432 * it's for him.
433 */
434 for(i=0; i<map->symbolset.numsymbols; i++) {
435 if (map->symbolset.symbol[i]!=NULL) {
436 symbolObj *s = map->symbolset.symbol[i];
437 if(s->renderer == altrenderer) {
438 altrenderer->freeSymbol(s);
439 s->renderer = NULL;
440 }
441 }
442 }
443
444 } else if(image != image_draw) {
445 rendererVTableObj *renderer = MS_IMAGE_RENDERER(image_draw);
446 rasterBufferObj rb;
447 memset(&rb,0,sizeof(rasterBufferObj));
448
449 ret = renderer->getRasterBufferHandle(image_draw,&rb);
450 if(UNLIKELY(ret == MS_FAILURE)) goto legend_icon_cleanup;
451 ret = renderer->mergeRasterBuffer(image,&rb,((lp->compositer)?lp->compositer->opacity*0.01:1.0),0,0,0,0,rb.width,rb.height);
452 if(UNLIKELY(ret == MS_FAILURE)) goto legend_icon_cleanup;
453
454 /* deref and possibly free temporary transparent output format. */
455 msApplyOutputFormat( &transFormat, NULL, MS_NOOVERRIDE, MS_NOOVERRIDE, MS_NOOVERRIDE );
456
457 }
458
459 legend_icon_cleanup:
460 if(image != image_draw) {
461 msFreeImage(image_draw);
462 }
463 return ret;
464 }
465
msCreateLegendIcon(mapObj * map,layerObj * lp,classObj * class,int width,int height,int scale_independant)466 imageObj *msCreateLegendIcon(mapObj* map, layerObj* lp, classObj* class, int width, int height, int scale_independant)
467 {
468 imageObj *image;
469 outputFormatObj *format = NULL;
470 int i = 0;
471
472 rendererVTableObj *renderer = MS_MAP_RENDERER(map);
473
474 if( !renderer ) {
475 msSetError(MS_MISCERR, "invalid map outputformat", "msCreateLegendIcon()");
476 return(NULL);
477 }
478
479 /* ensure we have an image format representing the options for the legend */
480 msApplyOutputFormat(&format, map->outputformat, map->legend.transparent, map->legend.interlace, MS_NOOVERRIDE);
481
482 image = msImageCreate(width,height,format,map->web.imagepath, map->web.imageurl,
483 map->resolution, map->defresolution, &(map->legend.imagecolor));
484
485 /* drop this reference to output format */
486 msApplyOutputFormat( &format, NULL, MS_NOOVERRIDE, MS_NOOVERRIDE, MS_NOOVERRIDE );
487
488 if(image == NULL) {
489 msSetError(MS_IMGERR, "Unable to initialize image.","msCreateLegendIcon()");
490 return(NULL);
491 }
492 image->map = map;
493
494 /* Call drawLegendIcon with destination (0, 0) */
495 /* Return an empty image if lp==NULL || class=NULL */
496 /* (If class is NULL draw the legend for all classes. Modifications done */
497 /* Fev 2004 by AY) */
498 if (lp) {
499 if (class) {
500 if(UNLIKELY(MS_FAILURE == msDrawLegendIcon(map, lp, class, width, height, image, 0, 0, scale_independant, NULL))) {
501 msFreeImage(image);
502 return NULL;
503 }
504 } else {
505 for (i=0; i<lp->numclasses; i++) {
506 if(UNLIKELY(MS_FAILURE == msDrawLegendIcon(map, lp, lp->class[i], width, height, image, 0, 0, scale_independant, NULL))) {
507 msFreeImage(image);
508 return NULL;
509 }
510 }
511 }
512 }
513 return image;
514 }
515
516 /*
517 * Calculates the optimal size for the legend. If the optional layerObj
518 * argument is given, the legend size will be calculated for only that
519 * layer. Otherwise, the legend size is calculated for all layers that
520 * are not MS_OFF or of MS_LAYER_QUERY type.
521 *
522 * Returns one of:
523 * MS_SUCCESS
524 * MS_FAILURE
525 */
msLegendCalcSize(mapObj * map,int scale_independent,int * size_x,int * size_y,int * layer_index,int num_layers,map_hittest * hittest,double resolutionfactor)526 int msLegendCalcSize(mapObj *map, int scale_independent, int *size_x, int *size_y,
527 int *layer_index, int num_layers, map_hittest *hittest,
528 double resolutionfactor)
529 {
530 int i, j;
531 int status, maxwidth=0, nLegendItems=0;
532 char *text;
533 layerObj *lp;
534 rectObj rect;
535 int current_layers=0;
536
537 /* reset sizes */
538 *size_x = 0;
539 *size_y = 0;
540
541 /* enable scale-dependent calculations */
542 if(!scale_independent) {
543 map->cellsize = msAdjustExtent(&(map->extent), map->width, map->height);
544 status = msCalculateScale(map->extent, map->units, map->width, map->height, map->resolution, &map->scaledenom);
545 if(status != MS_SUCCESS) return MS_FAILURE;
546 }
547
548 /*
549 * step through all map classes, and for each one that will be displayed
550 * calculate the label size
551 */
552 if (layer_index != NULL && num_layers >0)
553 current_layers = num_layers;
554 else
555 current_layers = map->numlayers;
556
557 for(i=0; i< current_layers; i++) {
558 int layerindex;
559 if (layer_index != NULL && num_layers > 0)
560 layerindex = layer_index[i];
561 else
562 layerindex = map->layerorder[i];
563
564 lp = (GET_LAYER(map, layerindex));
565
566 if((lp->status == MS_OFF && (layer_index == NULL || num_layers <= 0)) || (lp->type == MS_LAYER_QUERY)) /* skip it */
567 continue;
568
569 if(hittest && hittest->layerhits[layerindex].status == 0) continue;
570
571 if(!scale_independent && map->scaledenom > 0) {
572 if((lp->maxscaledenom > 0) && (map->scaledenom > lp->maxscaledenom)) continue;
573 if((lp->minscaledenom > 0) && (map->scaledenom <= lp->minscaledenom)) continue;
574 }
575
576 for(j=lp->numclasses-1; j>=0; j--) {
577 textSymbolObj ts;
578 text = lp->class[j]->title?lp->class[j]->title:lp->class[j]->name; /* point to the right legend text, title takes precedence */
579 if(!text) continue; /* skip it */
580
581 /* skip the class if the classgroup is defined */
582 if(lp->classgroup && (lp->class[j]->group == NULL || strcasecmp(lp->class[j]->group, lp->classgroup) != 0))
583 continue;
584
585 /* verify class scale */
586 if(!scale_independent && map->scaledenom > 0) {
587 if((lp->class[j]->maxscaledenom > 0) && (map->scaledenom > lp->class[j]->maxscaledenom)) continue;
588 if((lp->class[j]->minscaledenom > 0) && (map->scaledenom <= lp->class[j]->minscaledenom)) continue;
589 }
590 if(hittest && hittest->layerhits[layerindex].classhits[j].status == 0) continue;
591
592 if(*text) {
593 initTextSymbol(&ts);
594 msPopulateTextSymbolForLabelAndString(&ts,&map->legend.label,msStrdup(text),resolutionfactor,resolutionfactor, 0);
595 if(UNLIKELY(MS_FAILURE == msGetTextSymbolSize(map,&ts,&rect))) {
596 freeTextSymbol(&ts);
597 return MS_FAILURE;
598 }
599 freeTextSymbol(&ts);
600
601 maxwidth = MS_MAX(maxwidth, MS_NINT(rect.maxx - rect.minx));
602 *size_y += MS_MAX(MS_NINT(rect.maxy - rect.miny), map->legend.keysizey);
603 } else {
604 *size_y += map->legend.keysizey;
605 }
606 nLegendItems++;
607 }
608 }
609
610 /* Calculate the size of the legend: */
611 /* - account for the Y keyspacing */
612 *size_y += (2*VMARGIN) + ((nLegendItems-1) * map->legend.keyspacingy);
613 /* - determine the legend width */
614 *size_x = (2*HMARGIN) + maxwidth + map->legend.keyspacingx + map->legend.keysizex;
615
616 if(*size_y <=0 || *size_x <=0)
617 return MS_FAILURE;
618
619 return MS_SUCCESS;
620 }
621
622 /*
623 ** Creates a GD image of a legend for a specific map. msDrawLegend()
624 ** respects the current scale, and classes without a name are not
625 ** added to the legend.
626 **
627 ** scale_independent is used for WMS GetLegendGraphic. It should be set to
628 ** MS_FALSE in most cases. If it is set to MS_TRUE then the layers' minscale
629 ** and maxscale are ignored and layers that are currently out of scale still
630 ** show up in the legend.
631 */
msDrawLegend(mapObj * map,int scale_independent,map_hittest * hittest)632 imageObj *msDrawLegend(mapObj *map, int scale_independent, map_hittest *hittest)
633 {
634 int i,j,ret=MS_SUCCESS; /* loop counters */
635 pointObj pnt;
636 int size_x, size_y=0;
637 layerObj *lp;
638 rectObj rect;
639 imageObj *image = NULL;
640 outputFormatObj *format = NULL;
641 char *text;
642
643 struct legend_struct {
644 int height;
645 textSymbolObj ts;
646 int layerindex, classindex;
647 struct legend_struct* pred;
648 };
649 typedef struct legend_struct legendlabel;
650 legendlabel *head=NULL,*cur=NULL;
651
652 if(!MS_RENDERER_PLUGIN(map->outputformat)) {
653 msSetError(MS_MISCERR,"unsupported output format","msDrawLegend()");
654 return NULL;
655 }
656 if(msValidateContexts(map) != MS_SUCCESS) return NULL; /* make sure there are no recursive REQUIRES or LABELREQUIRES expressions */
657 if(msLegendCalcSize(map, scale_independent, &size_x, &size_y, NULL, 0, hittest, map->resolution/map->defresolution) != MS_SUCCESS) return NULL;
658
659 /*
660 * step through all map classes, and for each one that will be displayed
661 * keep a reference to its label size and text
662 */
663
664 for(i=0; i<map->numlayers; i++) {
665 lp = (GET_LAYER(map, map->layerorder[i]));
666
667 if((lp->status == MS_OFF) || (lp->type == MS_LAYER_QUERY)) /* skip it */
668 continue;
669
670 if(hittest && hittest->layerhits[map->layerorder[i]].status == 0)
671 continue;
672
673 if(!scale_independent && map->scaledenom > 0) {
674 if((lp->maxscaledenom > 0) && (map->scaledenom > lp->maxscaledenom)) continue;
675 if((lp->minscaledenom > 0) && (map->scaledenom <= lp->minscaledenom)) continue;
676 }
677
678 if(!scale_independent && lp->maxscaledenom <=0 && lp->minscaledenom <=0) {
679 if((lp->maxgeowidth > 0) && ((map->extent.maxx - map->extent.minx) > lp->maxgeowidth)) continue;
680 if((lp->mingeowidth > 0) && ((map->extent.maxx - map->extent.minx) < lp->mingeowidth)) continue;
681 }
682
683 /* set the scale factor so that scale dependant symbols are drawn in the legend with their default size */
684 if(lp->sizeunits != MS_PIXELS) {
685 map->cellsize = msAdjustExtent(&(map->extent), map->width, map->height);
686 lp->scalefactor = (msInchesPerUnit(lp->sizeunits,0)/msInchesPerUnit(map->units,0)) / map->cellsize;
687 }
688
689 for(j=lp->numclasses-1; j>=0; j--) {
690 text = lp->class[j]->title?lp->class[j]->title:lp->class[j]->name; /* point to the right legend text, title takes precedence */
691 if(!text) continue; /* skip it */
692
693 /* skip the class if the classgroup is defined */
694 if(lp->classgroup && (lp->class[j]->group == NULL || strcasecmp(lp->class[j]->group, lp->classgroup) != 0))
695 continue;
696
697 if(!scale_independent && map->scaledenom > 0) { /* verify class scale here */
698 if((lp->class[j]->maxscaledenom > 0) && (map->scaledenom > lp->class[j]->maxscaledenom)) continue;
699 if((lp->class[j]->minscaledenom > 0) && (map->scaledenom <= lp->class[j]->minscaledenom)) continue;
700 }
701
702 if(hittest && hittest->layerhits[map->layerorder[i]].classhits[j].status == 0) {
703 continue;
704 }
705
706 cur = (legendlabel*) msSmallMalloc(sizeof(legendlabel));
707 initTextSymbol(&cur->ts);
708 if(*text) {
709 msPopulateTextSymbolForLabelAndString(&cur->ts,&map->legend.label,msStrdup(text),map->resolution/map->defresolution,map->resolution/map->defresolution, 0);
710 if(UNLIKELY(MS_FAILURE == msComputeTextPath(map,&cur->ts))) {
711 ret = MS_FAILURE;
712 goto cleanup;
713 }
714 if(UNLIKELY(MS_FAILURE == msGetTextSymbolSize(map,&cur->ts,&rect))) {
715 ret = MS_FAILURE;
716 goto cleanup;
717 }
718 cur->height = MS_MAX(MS_NINT(rect.maxy - rect.miny), map->legend.keysizey);
719 } else {
720 cur->height = map->legend.keysizey;
721 }
722
723 cur->classindex = j;
724 cur->layerindex = map->layerorder[i];
725 cur->pred = head;
726 head = cur;
727 }
728 }
729
730
731 /* ensure we have an image format representing the options for the legend. */
732 msApplyOutputFormat(&format, map->outputformat, map->legend.transparent, map->legend.interlace, MS_NOOVERRIDE);
733
734 /* initialize the legend image */
735 image = msImageCreate(size_x, size_y, format, map->web.imagepath, map->web.imageurl, map->resolution, map->defresolution, &map->legend.imagecolor);
736 if(!image) {
737 msSetError(MS_MISCERR, "Unable to initialize image.", "msDrawLegend()");
738 return NULL;
739 }
740 image->map = map;
741
742 /* image = renderer->createImage(size_x,size_y,format,&(map->legend.imagecolor)); */
743
744 /* drop this reference to output format */
745 msApplyOutputFormat(&format, NULL, MS_NOOVERRIDE, MS_NOOVERRIDE, MS_NOOVERRIDE);
746
747 pnt.y = VMARGIN;
748 pnt.x = HMARGIN + map->legend.keysizex + map->legend.keyspacingx;
749
750 while(cur) { /* cur initially points on the last legend item, i.e. the one that should be at the top */
751 class_hittest *ch = NULL;
752
753 /* set the scale factor so that scale dependant symbols are drawn in the legend with their default size */
754 if(map->layers[cur->layerindex]->sizeunits != MS_PIXELS) {
755 map->cellsize = msAdjustExtent(&(map->extent), map->width, map->height);
756 map->layers[cur->layerindex]->scalefactor = (msInchesPerUnit(map->layers[cur->layerindex]->sizeunits,0)/msInchesPerUnit(map->units,0)) / map->cellsize;
757 }
758 if(hittest) {
759 ch = &hittest->layerhits[cur->layerindex].classhits[cur->classindex];
760 }
761 ret = msDrawLegendIcon(map, map->layers[cur->layerindex], map->layers[cur->layerindex]->class[cur->classindex], map->legend.keysizex, map->legend.keysizey, image, HMARGIN, (int) pnt.y, scale_independent, ch);
762 if(UNLIKELY(ret != MS_SUCCESS))
763 goto cleanup;
764
765 pnt.y += cur->height;
766
767 if(cur->ts.annotext) {
768 pointObj textPnt = pnt;
769 textPnt.y -= cur->ts.textpath->bounds.bbox.maxy;
770 textPnt.y += map->legend.label.offsety;
771 textPnt.x += map->legend.label.offsetx;
772 ret = msDrawTextSymbol(map,image,textPnt,&cur->ts);
773 if(UNLIKELY(ret == MS_FAILURE))
774 goto cleanup;
775 freeTextSymbol(&cur->ts);
776 }
777
778 pnt.y += map->legend.keyspacingy; /* bump y for next label */
779
780 /* clean up */
781 head = cur;
782 cur = cur->pred;
783 free(head);
784 } /* next legend */
785
786 cleanup:
787 while(cur) {
788 freeTextSymbol(&cur->ts);
789 head = cur;
790 cur = cur->pred;
791 free(head);
792 }
793 if(UNLIKELY(ret != MS_SUCCESS)) {
794 if(image) msFreeImage(image);
795 return NULL;
796 }
797 return(image);
798 }
799
800 /* TODO */
msEmbedLegend(mapObj * map,imageObj * img)801 int msEmbedLegend(mapObj *map, imageObj *img)
802 {
803 int s,l;
804 pointObj point;
805 imageObj *image = NULL;
806 symbolObj *legendSymbol;
807 char* imageType = NULL;
808
809 rendererVTableObj *renderer;
810
811 s = msGetSymbolIndex(&(map->symbolset), "legend", MS_FALSE);
812 if(s != -1)
813 msRemoveSymbol(&(map->symbolset), s); /* solves some caching issues in AGG with long-running processes */
814
815 if(msGrowSymbolSet(&map->symbolset) == NULL)
816 return -1;
817 s = map->symbolset.numsymbols;
818 legendSymbol = map->symbolset.symbol[s];
819 map->symbolset.numsymbols++;
820 initSymbol(legendSymbol);
821
822 if(!MS_RENDERER_PLUGIN(map->outputformat) || !MS_MAP_RENDERER(map)->supports_pixel_buffer) {
823 imageType = msStrdup(map->imagetype); /* save format */
824 if MS_DRIVER_CAIRO(map->outputformat)
825 map->outputformat = msSelectOutputFormat( map, "cairopng" );
826 else
827 map->outputformat = msSelectOutputFormat( map, "png" );
828
829 msInitializeRendererVTable(map->outputformat);
830 }
831 renderer = MS_MAP_RENDERER(map);
832
833 /* render the legend. */
834 image = msDrawLegend(map, MS_FALSE, NULL);
835 if( image == NULL ) {
836 msFree(imageType);
837 return MS_FAILURE;
838 }
839
840 if (imageType) {
841 map->outputformat = msSelectOutputFormat( map, imageType ); /* restore format */
842 msFree(imageType);
843 }
844
845 /* copy renderered legend image into symbol */
846 legendSymbol->pixmap_buffer = calloc(1,sizeof(rasterBufferObj));
847 MS_CHECK_ALLOC(legendSymbol->pixmap_buffer, sizeof(rasterBufferObj), MS_FAILURE);
848
849 if(MS_SUCCESS != renderer->getRasterBufferCopy(image,legendSymbol->pixmap_buffer))
850 return MS_FAILURE;
851 legendSymbol->renderer = renderer;
852
853 msFreeImage( image );
854
855 if(!legendSymbol->pixmap_buffer) return(MS_FAILURE); /* something went wrong creating scalebar */
856
857 legendSymbol->type = MS_SYMBOL_PIXMAP; /* intialize a few things */
858 legendSymbol->name = msStrdup("legend");
859 legendSymbol->sizex = legendSymbol->pixmap_buffer->width;
860 legendSymbol->sizey = legendSymbol->pixmap_buffer->height;
861
862 /* I'm not too sure this test is sufficient ... NFW. */
863 /* if(map->legend.transparent == MS_ON) */
864 /* gdImageColorTransparent(legendSymbol->img_deprecated, 0); */
865
866 switch(map->legend.position) {
867 case(MS_LL):
868 point.x = MS_NINT(legendSymbol->sizex/2.0);
869 point.y = map->height - MS_NINT(legendSymbol->sizey/2.0);
870 break;
871 case(MS_LR):
872 point.x = map->width - MS_NINT(legendSymbol->sizex/2.0);
873 point.y = map->height - MS_NINT(legendSymbol->sizey/2.0);
874 break;
875 case(MS_LC):
876 point.x = MS_NINT(map->width/2.0);
877 point.y = map->height - MS_NINT(legendSymbol->sizey/2.0);
878 break;
879 case(MS_UR):
880 point.x = map->width - MS_NINT(legendSymbol->sizex/2.0);
881 point.y = MS_NINT(legendSymbol->sizey/2.0);
882 break;
883 case(MS_UL):
884 point.x = MS_NINT(legendSymbol->sizex/2.0);
885 point.y = MS_NINT(legendSymbol->sizey/2.0);
886 break;
887 case(MS_UC):
888 point.x = MS_NINT(map->width/2.0);
889 point.y = MS_NINT(legendSymbol->sizey/2.0);
890 break;
891 }
892
893 l = msGetLayerIndex(map, "__embed__legend");
894 if(l == -1) {
895 if(msGrowMapLayers(map) == NULL)
896 return(-1);
897 l = map->numlayers;
898 map->numlayers++;
899 if(initLayer((GET_LAYER(map, l)), map) == -1) return(-1);
900 GET_LAYER(map, l)->name = msStrdup("__embed__legend");
901 GET_LAYER(map, l)->type = MS_LAYER_POINT;
902
903 if(msGrowLayerClasses( GET_LAYER(map, l) ) == NULL)
904 return(-1);
905 if(initClass(GET_LAYER(map, l)->class[0]) == -1) return(-1);
906 GET_LAYER(map, l)->numclasses = 1; /* so we make sure to free it */
907
908 /* update the layer order list with the layer's index. */
909 map->layerorder[l] = l;
910 }
911
912 GET_LAYER(map, l)->status = MS_ON;
913
914 if(map->legend.postlabelcache) { /* add it directly to the image */
915 if(UNLIKELY(msMaybeAllocateClassStyle(GET_LAYER(map, l)->class[0], 0)==MS_FAILURE)) return MS_FAILURE;
916 GET_LAYER(map, l)->class[0]->styles[0]->symbol = s;
917 if(UNLIKELY(MS_FAILURE == msDrawMarkerSymbol(map, img, &point, GET_LAYER(map, l)->class[0]->styles[0], 1.0)))
918 return MS_FAILURE;
919 } else {
920 if(!GET_LAYER(map, l)->class[0]->labels) {
921 if(msGrowClassLabels(GET_LAYER(map, l)->class[0]) == NULL) return MS_FAILURE;
922 initLabel(GET_LAYER(map, l)->class[0]->labels[0]);
923 GET_LAYER(map, l)->class[0]->numlabels = 1;
924 GET_LAYER(map, l)->class[0]->labels[0]->force = MS_TRUE;
925 GET_LAYER(map, l)->class[0]->labels[0]->size = MS_MEDIUM; /* must set a size to have a valid label definition */
926 GET_LAYER(map, l)->class[0]->labels[0]->priority = MS_MAX_LABEL_PRIORITY;
927 }
928 if(GET_LAYER(map, l)->class[0]->labels[0]->numstyles == 0) {
929 if(msGrowLabelStyles(GET_LAYER(map,l)->class[0]->labels[0]) == NULL)
930 return(MS_FAILURE);
931 GET_LAYER(map,l)->class[0]->labels[0]->numstyles = 1;
932 initStyle(GET_LAYER(map,l)->class[0]->labels[0]->styles[0]);
933 GET_LAYER(map,l)->class[0]->labels[0]->styles[0]->_geomtransform.type = MS_GEOMTRANSFORM_LABELPOINT;
934 }
935 GET_LAYER(map,l)->class[0]->labels[0]->styles[0]->symbol = s;
936 if(UNLIKELY(MS_FAILURE == msAddLabel(map, img, GET_LAYER(map, l)->class[0]->labels[0], l, 0, NULL, &point, -1, NULL)))
937 return MS_FAILURE;
938 }
939
940 /* Mark layer as deleted so that it doesn't interfere with html legends or with saving maps */
941 GET_LAYER(map, l)->status = MS_DELETE;
942
943 return MS_SUCCESS;
944 }
945