Steve, Shawn,<br><br>I think this makes a lot of sense (conceptually, I haven't looked at the code).<br><br>The current approach of Mapserver is far from perfect, and I've asked previously on the wms-dev list if it is allowed to leave out width and height in the LegendURL or use values of -1, but as you noticed WMS
1.3 has this possibility where 1.1 hasn't. <br><br>But if the server knows the legend image size correctly, it will always be the preferable way to include it in the GetCapabilities output.<br><br>So I'm +1.<br><br>
Best regards,<br>Bart<br><br><div class="gmail_quote">On Dec 11, 2007 11:40 PM, Shawn Gervais <<a href="mailto:project10@project10.net">project10@project10.net</a>> wrote:<br><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
Hi devs,<br><br>I have attached a patch which:<br> - Refactors legend size calculation out of msDrawLegend and into its<br>own function<br> - Uses the legend size calculating function to better estimate the<br>
height/width of LegendURLs advertised in GetCapabilities responses<br><br><br>There seems (to me) to be some ambiguity in the WMS specs regarding<br>LegendURL. WMS 1.3.0 clears this up a lot, and states (03-109r1, 7.2.4.6.5
):<br><br>"Servers should provide the width and height attributes if known at the<br>time of processing the GetCapabilities request."<br><br>And WMS 1.3.0 makes those attributes optional. However, under WMS 1.1.1
<br>MapServer advertises a GetLegendGraphic-using OnlineResource for the<br>LegendURL, but provides dimensions that are only sufficient for drawing<br>the legend 'key', but not the label.<br><br>Of course, the dimensions will (potentially) be incorrect if the client
<br>issues RULE or SCALE parameters, but they should be correct for the URL<br>that is advertised.<br><br>Any comments? Flames?<br><font color="#888888"><br>-Shawn<br></font><br>Index: maplegend.c<br>===================================================================
<br>--- maplegend.c (revision 7163)<br>+++ maplegend.c (working copy)<br>@@ -110,6 +110,104 @@<br> }<br><br> /*<br>+ * Calculates the optimal size for the legend<br>+ *<br>+ * Returns one of:<br>+ * MS_SUCCESS<br>+ * MS_FAILURE
<br>+ */<br>+int msLegendCalcSize(mapObj *map, int scale_independent, int *size_x,<br>+ int *size_y) {<br>+ int i, j;<br>+ int status, maxwidth=0, nLegendItems=0;<br>+ char *transformedText; // Label text after applying wrapping,
<br>+ // encoding if necessary<br>+ layerObj *lp;<br>+ rectObj rect;<br>+<br>+ /* Reset sizes */<br>+ *size_x = 0;<br>+ *size_y = 0;<br>+<br>+ /* Enable scale-dependent calculations */
<br>+ if (!scale_independent) {<br>+ map->cellsize = msAdjustExtent(&(map->extent), map->width, map->height);<br>+ status = msCalculateScale(map->extent, map->units, map->width,<br>
+ map->height, map->resolution, &map->scaledenom);<br>+ if (status != MS_SUCCESS) return MS_FAILURE;<br>+ }<br>+<br>+ /*<br>+ * step through all map classes, and for each one that will be displayed
<br>+ * calculate the label size<br>+ */<br>+ for (i=0; i<map->numlayers; i++) {<br>+ lp = (GET_LAYER(map, map->layerorder[i]));<br>+<br>+ if ((lp->status == MS_OFF) || (lp->type == MS_LAYER_QUERY)) /* skip it */
<br>+ continue;<br>+<br>+ if (!scale_independent && map->scaledenom > 0) {<br>+ if ((lp->maxscaledenom > 0) && (map->scaledenom > lp->maxscaledenom))<br>+ continue;
<br>+ if ((lp->minscaledenom > 0) && (map->scaledenom <= lp->minscaledenom))<br>+ continue;<br>+ }<br>+<br>+ for (j=lp->numclasses-1; j>=0; j--) {<br>+ if (!lp->class[j]->name) continue; /* skip it */
<br>+<br>+ /* Verify class scale */<br>+ if (!scale_independent && map->scaledenom > 0) {<br>+ if ( (lp->class[j]->maxscaledenom > 0)<br>+ && (map->scaledenom > lp->class[j]->maxscaledenom))
<br>+ continue;<br>+<br>+ if ( (lp->class[j]->minscaledenom > 0)<br>+ && (map->scaledenom <= lp->class[j]->minscaledenom))<br>+ continue;
<br>+ }<br>+<br>+ /*<br>+ * apply encoding and line wrapping to the legend label if requested<br>+ * this is done conditionnally as the text transformation function<br>+ * does some memory allocations that can be avoided in most cases.
<br>+ * the transformed text must be freed once finished, this must be done<br>+ * conditionnally by testing if the transformed text pointer is the<br>+ * same as the class name pointer
<br>+ */<br>+ if (map->legend.label.encoding || map->legend.label.wrap)<br>+ transformedText = msTransformLabelText(&map->legend.label,<br>+ lp->class[j]->name);
<br>+ else<br>+ transformedText = lp->class[j]->name;<br>+<br>+ if ( transformedText == NULL<br>+ || msGetLabelSize(transformedText, &map->legend.label,<br>
+ &rect, &(map->fontset), 1.0, MS_FALSE) != 0)<br>+ { /* something bad happened */<br>+ if (transformedText != lp->class[j]->name)<br>+ free(transformedText);
<br>+<br>+ return MS_FAILURE;<br>+ }<br>+<br>+ maxwidth = MS_MAX(maxwidth, MS_NINT(rect.maxx - rect.minx));<br>+ *size_y += MS_MAX(MS_NINT(rect.maxy - rect.miny), map->legend.keysizey
);<br>+ nLegendItems++;<br>+ }<br>+ }<br>+<br>+ /* Calculate the size of the legend: */<br>+ /* - account for the Y keyspacing */<br>+ *size_y += (2*VMARGIN) + ((nLegendItems-1) * map->legend.keyspacingy
);<br>+ /* - determine the legend width */<br>+ *size_x = (2*HMARGIN) + maxwidth + map->legend.keyspacingx + map->legend.keysizex;<br>+<br>+ return MS_SUCCESS;<br>+}<br>+<br>+/*<br> ** Creates a GD image of a legend for a specific map. msDrawLegend()
<br> ** respects the current scale, and classes without a name are not<br> ** added to the legend.<br>@@ -121,14 +219,11 @@<br> */<br> imageObj *msDrawLegend(mapObj *map, int scale_independent)<br> {<br>- int status;<br>
-<br> gdImagePtr img; /* image data structure */<br> int i,j; /* loop counters */<br> pointObj pnt;<br> int size_x, size_y=0;<br> layerObj *lp;<br>- int maxwidth=0,nLegendItems=0;<br> rectObj rect;
<br> imageObj *image = NULL;<br> outputFormatObj *format = NULL;<br>@@ -142,14 +237,12 @@<br> typedef struct legend_struct legendlabel;<br> legendlabel *head=NULL,*cur=NULL;<br><br>- if (!scale_independent) {
<br>- map->cellsize = msAdjustExtent(&(map->extent), map->width, map->height);<br>- status = msCalculateScale(map->extent, map->units, map->width, map->height, map->resolution, &map->scaledenom);
<br>- if(status != MS_SUCCESS) return(NULL);<br>- }<br>+<br><br> if(msValidateContexts(map) != MS_SUCCESS) return NULL; /* make sure there are no recursive REQUIRES or LABELREQUIRES expressions */<br><br>+ if(msLegendCalcSize(map, scale_independent, &size_x, &size_y) != MS_SUCCESS) return NULL;
<br>+<br> /*<br> * step through all map classes, and for each one that will be displayed<br> * keep a reference to its label size and text<br>@@ -201,19 +294,10 @@<br> }<br> return(NULL);
<br> }<br>- maxwidth = MS_MAX(maxwidth, MS_NINT(rect.maxx - rect.minx));<br> cur->height = MS_MAX(MS_NINT(rect.maxy - rect.miny), map->legend.keysizey);<br>- size_y+=cur->height;
<br>- nLegendItems++;<br> }<br> }<br><br>- /*<br>- ** Calculate the optimal image size for the legend<br>- */<br>- size_y += (2*VMARGIN) + ((nLegendItems-1)*map->legend.keyspacingy); /*initial vertical size*/
<br>- size_x = (2*HMARGIN)+(maxwidth)+(map->legend.keyspacingx)+(map->legend.keysizex);<br>-<br> /* ensure we have an image format representing the options for the legend. */<br> msApplyOutputFormat(&format, map->outputformat, map->
legend.transparent, map->legend.interlace, MS_NOOVERRIDE);<br><br>Index: mapserver.h<br>===================================================================<br>--- mapserver.h (revision 7163)<br>+++ mapserver.h (working copy)
<br>@@ -1648,6 +1648,7 @@<br> MS_DLL_EXPORT void freeImageCache(struct imageCacheObj *ic);<br><br> MS_DLL_EXPORT imageObj *msDrawLegend(mapObj *map, int scale_independent); /* in maplegend.c */<br>+MS_DLL_EXPORT int msLegendCalcSize(mapObj *map, int scale_independent, int *size_x, int *size_y);
<br> MS_DLL_EXPORT int msEmbedLegend(mapObj *map, imageObj *img);<br> MS_DLL_EXPORT int msDrawLegendIcon(mapObj* map, layerObj* lp, classObj* myClass, int width, int height, imageObj *img, int dstX, int dstY);<br> MS_DLL_EXPORT imageObj *msCreateLegendIcon(mapObj* map, layerObj* lp, classObj* myClass, int width, int height);
<br>Index: mapwms.c<br>===================================================================<br>--- mapwms.c (revision 7163)<br>+++ mapwms.c (working copy)<br>@@ -1029,6 +1029,24 @@<br> }<br> }<br><br>+/*<br>+ * msWMSGetLegendURLSize() - Estimates the size of a GetLegendGraphic result,
<br>+ * for a specific layer<br>+ */<br>+void msWMSGetLegendURLSize(mapObj *map, layerObj *lp, int *height, int *width) {<br>+ int i;<br>+<br>+ /* Turn off all layers other than the requested layer, required */
<br>+ /* for msLegendCalcSize() */<br>+ for (i=0; i<map->numlayers; i++) {<br>+ if (GET_LAYER(map, i) == lp)<br>+ GET_LAYER(map, i)->status = MS_ON;<br>+ else<br>+ GET_LAYER(map, i)->status = MS_OFF;
<br>+ }<br>+<br>+ msLegendCalcSize(map, 1, height, width); // Calculate scale-independent legend dimensions<br>+}<br><br> /*<br> ** msDumpLayer()<br>@@ -1286,16 +1304,11 @@<br> }<br> if (classnameset)
<br> {<br>- if (map->legend.keysizex > 0)<br>- sprintf(width, "%d", map->legend.keysizex);<br>- else<br>- sprintf(width, "%d", 20);/* default; */
<br>+ int size_x, size_y;<br>+ msWMSGetLegendURLSize(map, lp, &size_x, &size_y);<br>+ sprintf(width, "%d", size_x);<br>+ sprintf(height, "%d", size_y);
<br><br>- if (map->legend.keysizey > 0)<br>- sprintf(height, "%d", map->legend.keysizey);<br>- else<br>- sprintf(height, "%d", 20);/* default; */
<br>-<br> legendurl = (char*)malloc(strlen(script_url_encoded)+200);<br><br> #ifdef USE_GD_PNG<br><br></blockquote></div><br>