Legend size calculation, WMS LegendURL size

Shawn Gervais project10 at PROJECT10.NET
Tue Dec 11 17:40:41 EST 2007


Hi devs,

I have attached a patch which:
	- Refactors legend size calculation out of msDrawLegend and into its 
own function
	- Uses the legend size calculating function to better estimate the 
height/width of LegendURLs advertised in GetCapabilities responses


There seems (to me) to be some ambiguity in the WMS specs regarding 
LegendURL. WMS 1.3.0 clears this up a lot, and states (03-109r1, 7.2.4.6.5):

"Servers should provide the width and height attributes if known at the 
time of processing the GetCapabilities request."

And WMS 1.3.0 makes those attributes optional. However, under WMS 1.1.1 
MapServer advertises a GetLegendGraphic-using OnlineResource for the 
LegendURL, but provides dimensions that are only sufficient for drawing 
the legend 'key', but not the label.

Of course, the dimensions will (potentially) be incorrect if the client 
issues RULE or SCALE parameters, but they should be correct for the URL 
that is advertised.

Any comments? Flames?

-Shawn
-------------- next part --------------
Index: maplegend.c
===================================================================
--- maplegend.c	(revision 7163)
+++ maplegend.c	(working copy)
@@ -110,6 +110,104 @@
 }
 
 /*
+ * Calculates the optimal size for the legend
+ *     
+ * Returns one of:
+ *   MS_SUCCESS
+ *   MS_FAILURE
+ */
+int msLegendCalcSize(mapObj *map, int scale_independent, int *size_x,
+                     int *size_y) {
+    int i, j;
+    int status, maxwidth=0, nLegendItems=0;
+    char *transformedText; // Label text after applying wrapping,
+                           // encoding if necessary
+    layerObj *lp;  
+    rectObj rect;
+    
+    /* Reset sizes */
+    *size_x = 0;
+    *size_y = 0;
+    
+    /* Enable scale-dependent calculations */
+    if (!scale_independent) {
+        map->cellsize = msAdjustExtent(&(map->extent), map->width, map->height);
+        status = msCalculateScale(map->extent, map->units, map->width,
+                                  map->height, map->resolution, &map->scaledenom);
+        if (status != MS_SUCCESS) return MS_FAILURE;
+    }
+    
+    /*
+     * step through all map classes, and for each one that will be displayed
+     * calculate the label size
+     */
+    for (i=0; i<map->numlayers; i++) {
+        lp = (GET_LAYER(map, map->layerorder[i]));
+
+        if ((lp->status == MS_OFF) || (lp->type == MS_LAYER_QUERY)) /* skip it */
+            continue;
+            
+        if (!scale_independent && map->scaledenom > 0) {
+            if ((lp->maxscaledenom > 0) && (map->scaledenom > lp->maxscaledenom))
+                continue;
+            if ((lp->minscaledenom > 0) && (map->scaledenom <= lp->minscaledenom))
+                continue;
+        }
+        
+        for (j=lp->numclasses-1; j>=0; j--) {
+            if (!lp->class[j]->name) continue; /* skip it */
+            
+            /* Verify class scale */
+            if (!scale_independent && map->scaledenom > 0) {
+                if (   (lp->class[j]->maxscaledenom > 0) 
+                    && (map->scaledenom > lp->class[j]->maxscaledenom))
+                    continue;
+                    
+                if (   (lp->class[j]->minscaledenom > 0)
+                    && (map->scaledenom <= lp->class[j]->minscaledenom))
+                    continue;
+            }
+            
+            /*
+             * apply encoding and line wrapping to the legend label if requested
+             * this is done conditionnally as the text transformation function
+             * does some memory allocations that can be avoided in most cases.
+             * the transformed text must be freed once finished, this must be done
+             * conditionnally by testing if the transformed text pointer is the
+             * same as the class name pointer
+             */
+            if (map->legend.label.encoding || map->legend.label.wrap)
+                transformedText = msTransformLabelText(&map->legend.label,
+                                                       lp->class[j]->name);
+            else
+                transformedText = lp->class[j]->name;
+
+            if (   transformedText == NULL
+                || msGetLabelSize(transformedText, &map->legend.label, 
+                                  &rect, &(map->fontset), 1.0, MS_FALSE) != 0)
+            { /* something bad happened */
+                if (transformedText != lp->class[j]->name)
+                    free(transformedText);
+
+                return MS_FAILURE;
+            }
+
+            maxwidth = MS_MAX(maxwidth, MS_NINT(rect.maxx - rect.minx));
+            *size_y += MS_MAX(MS_NINT(rect.maxy - rect.miny), map->legend.keysizey);
+            nLegendItems++;
+        }
+    }
+
+    /* Calculate the size of the legend: */
+    /*   - account for the Y keyspacing */
+    *size_y += (2*VMARGIN) + ((nLegendItems-1) * map->legend.keyspacingy);
+    /*   - determine the legend width */
+    *size_x = (2*HMARGIN) + maxwidth + map->legend.keyspacingx + map->legend.keysizex;
+    
+    return MS_SUCCESS;
+}
+
+/*
 ** Creates a GD image of a legend for a specific map. msDrawLegend()
 ** respects the current scale, and classes without a name are not
 ** added to the legend.
@@ -121,14 +219,11 @@
 */
 imageObj *msDrawLegend(mapObj *map, int scale_independent)
 {
-    int status;
-
     gdImagePtr img; /* image data structure */
     int i,j; /* loop counters */
     pointObj pnt;
     int size_x, size_y=0;
     layerObj *lp;  
-    int maxwidth=0,nLegendItems=0;
     rectObj rect;
     imageObj *image = NULL;
     outputFormatObj *format = NULL;
@@ -142,14 +237,12 @@
     typedef struct legend_struct legendlabel;
     legendlabel *head=NULL,*cur=NULL;
 
-    if (!scale_independent) {
-        map->cellsize = msAdjustExtent(&(map->extent), map->width, map->height);
-        status = msCalculateScale(map->extent, map->units, map->width, map->height, map->resolution, &map->scaledenom);
-        if(status != MS_SUCCESS) return(NULL);
-    }
+    
 
     if(msValidateContexts(map) != MS_SUCCESS) return NULL; /* make sure there are no recursive REQUIRES or LABELREQUIRES expressions */
 
+    if(msLegendCalcSize(map, scale_independent, &size_x, &size_y) != MS_SUCCESS) return NULL;
+
     /*
      * step through all map classes, and for each one that will be displayed
      * keep a reference to its label size and text
@@ -201,19 +294,10 @@
                 }
                 return(NULL); 
             }
-            maxwidth = MS_MAX(maxwidth, MS_NINT(rect.maxx - rect.minx));
             cur->height = MS_MAX(MS_NINT(rect.maxy - rect.miny), map->legend.keysizey);
-            size_y+=cur->height;
-            nLegendItems++;
         }
     }
 
-    /*
-     ** Calculate the optimal image size for the legend
-     */
-    size_y += (2*VMARGIN) + ((nLegendItems-1)*map->legend.keyspacingy); /*initial vertical size*/
-    size_x = (2*HMARGIN)+(maxwidth)+(map->legend.keyspacingx)+(map->legend.keysizex);
-
     /* ensure we have an image format representing the options for the legend. */
     msApplyOutputFormat(&format, map->outputformat, map->legend.transparent, map->legend.interlace, MS_NOOVERRIDE);
 
Index: mapserver.h
===================================================================
--- mapserver.h	(revision 7163)
+++ mapserver.h	(working copy)
@@ -1648,6 +1648,7 @@
 MS_DLL_EXPORT void freeImageCache(struct imageCacheObj *ic);
 
 MS_DLL_EXPORT imageObj *msDrawLegend(mapObj *map, int scale_independent); /* in maplegend.c */
+MS_DLL_EXPORT int msLegendCalcSize(mapObj *map, int scale_independent, int *size_x, int *size_y);
 MS_DLL_EXPORT int msEmbedLegend(mapObj *map, imageObj *img);
 MS_DLL_EXPORT int msDrawLegendIcon(mapObj* map, layerObj* lp, classObj* myClass, int width, int height, imageObj *img, int dstX, int dstY);
 MS_DLL_EXPORT imageObj *msCreateLegendIcon(mapObj* map, layerObj* lp, classObj* myClass, int width, int height);
Index: mapwms.c
===================================================================
--- mapwms.c	(revision 7163)
+++ mapwms.c	(working copy)
@@ -1029,6 +1029,24 @@
   }
 }
 
+/*
+ * msWMSGetLegendURLSize() - Estimates the size of a GetLegendGraphic result,
+ *                           for a specific layer
+ */
+void msWMSGetLegendURLSize(mapObj *map, layerObj *lp, int *height, int *width) {
+    int i;
+    
+    /* Turn off all layers other than the requested layer, required */
+    /* for msLegendCalcSize() */
+    for (i=0; i<map->numlayers; i++) {
+        if (GET_LAYER(map, i) == lp)
+            GET_LAYER(map, i)->status = MS_ON;
+        else
+            GET_LAYER(map, i)->status = MS_OFF;
+    }
+    
+    msLegendCalcSize(map, 1, height, width); // Calculate scale-independent legend dimensions
+}
 
 /*
 ** msDumpLayer()
@@ -1286,16 +1304,11 @@
                    }
                    if (classnameset)
                    {
-                       if (map->legend.keysizex > 0)
-                         sprintf(width, "%d", map->legend.keysizex);
-                       else
-                         sprintf(width, "%d", 20);/* default; */
+                       int size_x, size_y;
+                       msWMSGetLegendURLSize(map, lp, &size_x, &size_y);
+                       sprintf(width,  "%d", size_x);
+                       sprintf(height, "%d", size_y);
 
-                       if (map->legend.keysizey > 0)
-                          sprintf(height, "%d", map->legend.keysizey);
-                       else
-                         sprintf(height, "%d", 20);/* default; */
-                   
                        legendurl = (char*)malloc(strlen(script_url_encoded)+200);
 
 #ifdef USE_GD_PNG


More information about the mapserver-dev mailing list