Path-following labels function (MapScript)

Paul james pauljame at GMAIL.COM
Thu Feb 2 10:23:10 EST 2006


Anybody already implemented/compiled that? Docs? Samples?

ty


*Date:*         Mon, 25 Apr 2005 10:50:38 -0500*Reply-To:*     James
Sohr <jms4 at PO.CWRU.EDU>*Sender:*       UMN MapServer Users List
<MAPSERVER-USERS at LISTS.UMN.EDU>*From:*         James Sohr
<jms4 at PO.CWRU.EDU>
<http://lists.umn.edu/cgi-bin/wa?A2=ind0504&L=mapserver-users&D=0&I=-3&X=7F3DD71C46DE6DE500&Y=maluche%40inf.ufsc.br&P=103721>*Subject:*
     Path-following labels function (MapScript)


As part of a larger project to help implement a high-quality labeling
system for MapServer, I've written an alpha-level Mapscript function,
label_layer_wpft(), which will draw path-following text labels,
returning it as a GD true color image which you can then overlay onto
your map image.



Here are some sample images of it in action:

The good:
http://img171.echo.cx/img171/7012/good13fx.gifhttp://img191.echo.cx/img191/8636/good21nf.gif

The bad:
http://img152.echo.cx/img152/4066/bad0yq.gif

And the ugly:
http://img152.echo.cx/img152/2049/ugly3uq.gif


To use the function, include it in your main mapscript routine.
The format for it's use is as follows:

  $mylabelimage = label_layer_wpft(  MAP, LAYER_NAME, LABEL_FORMAT);

  where MAP is a map object, LAYER_NAME is a string containing the name
  of the layer you wish to label, and LABEL_FORMAT is a string containing
  the format of the label (similiar to the TEXTITEM field in a map file).

For instance, to create a label image for a layer called "anno_roads",
with labels comprised of the street name (STNAME) and street type (STTYPE)
fields, use:

 $road_labels = label_layer_wpft($map, "anno_roads", "[STNAME] [STTYPE]");


You can then overlay this image onto your map image using the appropriate
GD functions.

It creates labels for a single layer by performing a query for the current
map extent against that layer, and then comparing each result to each
class within the layer.  It then uses each class's label attributes
(such as font, font size, and color) to draw the path-following label.

To optimize labeling, the function will automatically combine features
with common endpoints and common labels into a single feature.

Currently, there are quite a few limiations with this function:

   First of all, it only recongizes classes within a layer using the
   CLASSITEM feature only (i.e. each expression must be a regular
   epxression which is compared to the CLASSITEM).

   It only uses the class label's font, font size, and font color
   attributes -- it ignores all of the other label attributes (such
   as position, background color, border color, antialias, buffer, etc.)

   The function only does a rudimentary check to see if the label will
   fit on the feature.  Currently, the label is automatically centered
   along the feature's length, and no collision detection is done with
   other labels.

   The font name used in the labeling does not use the map's font file--
   hence the font name used for each class must be the actual true-type
   font filename (without the .ttf).  For instance, to label with
   the font arial bold, use "FONT arialbd" in your map file.


Like I said, the function is pretty basic so far... but I plan to make
a lot of improvements as time allows.

If you make any improvements you'd like to share, or have any questions
about the function or how to use it, feel free to let me know.

Without further adieu, here are the functions:


///////////////////////////////////////////////////////////////
//
// function label_layer_wpft()
//
// Draws path following text labels for a given layer.
//


function label_layer_wpft($map, $layername,$text){

$im = imagecreatetruecolor($map->width, $map->height);
$scale=1;
$layer=$map->getLayerByName($layername);
$rect=$map->extent;
$layer->set(template,"dummy");
$void=@$layer->queryByRect( $rect);

$mystring=$text;

$f=0;
while(preg_match("/\[/", $mystring) > 0){

if (strpos($mystring,"[")> 0){
      $text_array[$f]=substr($mystring,0,strpos($mystring,"["));
      $mystring=substr($mystring,strpos($mystring,"["));
      $f++;
    }

$g=strpos($mystring,"]");
$text_array[$f]=substr($mystring,0, $g + 1);
$f++;
$mystring=substr($mystring,$g + 1);
}

$text_array[$f]=$mystring;














$layer->open();
$uroads = 0;



 for ($i=0;$i<$layer->numclasses;$i++){
        $class = $layer->getclass($i);
        $label = $class->label;
        $label->set(minfeaturesize,99999999);
      }





for ($g=0;$g<$layer->getNumResults();$g++) {

$result = $layer->getResult($g);
$feature = $layer->getShape(-1,$result->shapeindex);
$classvalue = $feature->values[$layer->classitem];
$sname = $feature->values["FENAME"];


 for ($i=0;$i<$layer->numclasses;$i++){
        $class = $layer->getclass($i);
        $label = $class->label;
        if (preg_match($class->getExpression(), $classvalue) || $class-
>getExpression() == '')
    {

               $textlabel = "";
             for ($z=0;$z<count($text_array);$z++){
              if (preg_match('/\[*\]/', $text_array[$z]))
                { $textlabel = $textlabel.$feature->values[substr
($text_array[$z], 1, strlen($text_array[$z])-2)]; }
                else
                { $textlabel = $textlabel.$text_array[$z]; }
                }




for ($h=0;$h< $feature->numlines;$h++){
    $line = $feature->line($h);

     $line->project(ms_newprojectionobj($layer->getProjection
()),ms_newprojectionobj($map->getProjection()));
     $myx=array();
    $myy=array();
    for ($j=0; $j< $line->numpoints;$j++){
      $point = $line->point($j);
      $px = ($point->x - $rect->minx) / ($rect->maxx - $rect->minx) * $map-
>width / $scale;
      $py = $map->height / $scale - ($point->y - $rect->miny) / ($rect-
>maxy - $rect->miny) * $map->height / $scale ;
      $myx[$j]=$px;
      $myy[$j]=$py;
     }


$fnode = (int)($myx[0])."-".(int)($myy[0]);
$tnode = (int)$myx[count($myx)-1]."-".(int)($myy[count($myx)-1]);
$fename = $textlabel;
$xarray = $myx;
$yarray = $myy;



for ($m=0;$m<$uroads; $m++)
  {

  // Coallate features with matching endpoints and labels

  if ($tnode == $FNODES[$m] && $fename == $FENAMES[$m])
          {  $tnode = $TNODES[$m];
             $xarray = array_merge($xarray, $XARRAYS[$m]);
             $yarray = array_merge($yarray, $YARRAYS[$m]);
             $FNODES = array_trim($FNODES, $m);
             $FENAMES = array_trim($FENAMES, $m);
             $TNODES = array_trim($TNODES, $m);
             $XARRAYS = array_trim($XARRAYS, $m);
             $YARRAYS = array_trim($YARRAYS, $m);
             $FNARRAYS= array_trim($FNARRAYS, $m);
             $FSARRAYS = array_trim($FSARRAYS, $m);
             $FCARRAYS = array_trim($FCARRAYS, $m);

             $uroads = $uroads - 1;

           }


  if ($fnode == $TNODES[$m] && $fename == $FENAMES[$m])
          {  $fnode = $FNODES[$m];
             $xarray = array_merge($XARRAYS[$m], $xarray);
             $yarray = array_merge($YARRAYS[$m], $yarray);
             $FNODES = array_trim($FNODES, $m);
             $FENAMES = array_trim($FENAMES, $m);
             $TNODES = array_trim($TNODES, $m);
             $XARRAYS = array_trim($XARRAYS, $m);
             $YARRAYS = array_trim($YARRAYS, $m);
             $FNARRAYS= array_trim($FNARRAYS, $m);
             $FSARRAYS = array_trim($FSARRAYS, $m);
             $FCARRAYS = array_trim($FCARRAYS, $m);
             $uroads = $uroads - 1;

           }

   if ($fnode == $FNODES[$m] && $fename == $FENAMES[$m])
           {  $fnode = $tnode;
             $tnode = $TNODES[$m];

             $xarray = array_merge( array_reverse($xarray), $XARRAYS[$m]);
             $yarray = array_merge( array_reverse($yarray),$YARRAYS[$m]);
             $FNODES = array_trim($FNODES, $m);
             $FENAMES = array_trim($FENAMES, $m);
             $TNODES = array_trim($TNODES, $m);
             $XARRAYS = array_trim($XARRAYS, $m);
             $YARRAYS = array_trim($YARRAYS, $m);
             $FNARRAYS= array_trim($FNARRAYS, $m);
             $FSARRAYS = array_trim($FSARRAYS, $m);
             $FCARRAYS = array_trim($FCARRAYS, $m);
             $uroads = $uroads - 1;

           }



   if ($tnode == $TNODES[$m] && $fename == $FENAMES[$m])
          {  $tnode = $fnode;
             $fnode = $FNODES[$m];
             $xarray = array_merge($XARRAYS[$m], array_reverse($xarray));
             $yarray = array_merge($YARRAYS[$m], array_reverse($yarray));
             $FNODES = array_trim($FNODES, $m);
             $FENAMES = array_trim($FENAMES, $m);
             $TNODES = array_trim($TNODES, $m);
             $XARRAYS = array_trim($XARRAYS, $m);
             $YARRAYS = array_trim($YARRAYS, $m);
             $FNARRAYS= array_trim($FNARRAYS, $m);
             $FSARRAYS = array_trim($FSARRAYS, $m);
             $FCARRAYS = array_trim($FCARRAYS, $m);
             $uroads = $uroads - 1;

           }




   }

  // Add the current entry to the list

  $TNODES[$uroads] = $tnode;
  $FNODES[$uroads] = $fnode;
  $XARRAYS[$uroads] = $xarray;
  $YARRAYS[$uroads] = $yarray;
  $FENAMES[$uroads] = $fename;
  $FNARRAYS[$uroads] = $label->font;
  $FSARRAYS[$uroads] = $label->size;
  $FCARRAYS[$uroads] = $label->color;
  $uroads++;

}

$i = $layer->numclasses;
}
}
}



for ($m=0;$m<$uroads;$m++){

 label_path($im, $XARRAYS[$m], $YARRAYS[$m], $FNARRAYS[$m].".ttf",
$FSARRAYS[$m] , $FCARRAYS[$m], $FENAMES[$m]);

 }
return $im;
}



///////////////////////////////////////////////////////////////
//
// function label_Path()
//
// Takes a single feature and path, and writes a label fitted to that path.
// No collision detection is used, and only a rudimentary method is used to
// determine whether it fits or not.
//
// using this function, all labels must be drawn as seperate annotation
layers.
//
// the .map file should be configured to output as png for transparency.
//
///////////////////////////////////////////////////////////////

function label_path($im, $px, $py, $font, $fontsize, $color, $label){


$SMOOTH_LEVEL=2;    // Sets number of "smoothing steps" for text angles
$TEXT_SPACING=1.3;  // Set spacing multiplier for text

$imcolor = imagecolorallocate($im, $color->red,$color->green,$color->blue);

$numpoints = count($px);


// If line endpoint is horizontally before start point, reverse order
// This helps prevent (but doesn't completely eliminate) upside down and
backward labels

if ($px[0] > $px[$numpoints - 1]){
    $px = array_reverse($px);
    $py = array_reverse($py);
  }


$pathlength=0;



$textsz = imagettfbbox($fontsize, 0, $font, $label);
$labelength = ($textsz[2] - $textsz[0]) * $TEXT_SPACING;

for ($i=0;$i<$numpoints - 1;$i++){
  $pathlength = $pathlength + sqrt( ($px[$i + 1]  - $px[$i]) * ($px[$i +
1] - $px[$i]) + ($py[$i + 1] - $py[$i]) * ($py[$i + 1] - $py[$i]));
}



// If length of label exceeds length of path to draw on, doing nothing

if ($labelength > $pathlength) { return; }



$s= ($pathlength - $labelength)/2 - 10;   // Start value


$curlength = sqrt(   ($px[1]  - $px[0]) * ($px[1] - $px[0]) + ($py[1] - $py
[0]) * ($py[1] - $py[0])        );



if ($px[0] == $px[1]){

     if($py[0] < $py[1]) { $angle = 90;}
             else { $angle = -90;}}


else { $angle = rad2deg(atan2(($py[1] - $py[0]),($px[1] - $px[0])))  ; }


$i=0;
$curp=0;

$labelx=array();
$labely=array();
$angles=array();


while ($i < strlen($label) && $curp < $numpoints - 1) {


if ( $s < $curlength ) {
    $textsz = imagettfbbox($fontsize, 0, $font, $label[$i]);
    $pcwid = $cwid;
    $cwid = $textsz[2] - $textsz[0];
    $chgt = $textsz[5] - $textsz[1];
    $labelx[$i] = $px[$curp] + ($px[$curp + 1] - $px[$curp]) * ($s /
$curlength);
    $labely[$i] = $py[$curp] + ($py[$curp + 1] - $py[$curp]) * ($s /
$curlength);
    $angles[$i] = $angle;
$placement_ok = 1;

for ($g=0; $g < $i; $g++){



     if ( ($labelx[$i] - $labelx[$g]) * ($labelx[$i] - $labelx[$g]) +
($labely[$i] - $labely[$g]) * ($labely[$i] - $labely[$g]) < ($cwid *
$cwid) )
            {
                 $placement_ok = 0; }
   }





    $angles[$i] =  $angle;

 if ($placement_ok == 1){
     $s = $s + $TEXT_SPACING * $cwid  ;
    $i++;
    }

 else { $s = $s + 1; }

}


else  {
    $curp++;
    if ($curp < $numpoints - 1 ){
       $s = $s - $curlength;
       $curlength = sqrt(   ($px[$curp + 1]  - $px[$curp]) * ($px[$curp +
1] - $px[$curp]) + ($py[$curp + 1] - $py[$curp]) * ($py[$curp + 1] - $py
[$curp])        );
       if ($px[$curp] == $px[$curp + 1]){

             if($py[$curp] < $py[$curp + 1]) { $angle = 90;}
             else { $angle = -90;}}
         else { $angle = rad2deg(atan2(($py[$curp + 1] - $py[$curp]),($px
[$curp + 1] - $px[$curp])))  ; }

   } // END IF of $curp < $numpoints
   } // END ELSE

}   // END WHILE











for ($i=0; $i<$SMOOTH_LEVEL;$i++){
$tangles=$angles;
$angles[0]=(2 * $tangles[0] + $tangles[1]) / 3;
$angles[strlen($label) - 1] = (2 * $tangles[strlen($label) - 1] + $tangles
[strlen($label) - 2]) / 3;
for ( $j=1; $j< strlen($label)-1;$j++){
     $angles[$j] = ($tangles[$j - 1] + 2 * $tangles[$j] + $tangles[$j +
1]) / 4;
}
}




for ( $i=0; $i< strlen($label);$i++){
    imagettftext($im, $fontsize, -$angles[$i] , $labelx[$i] - 4 * sin
(deg2rad($angles[$i])), $labely[$i] + 4 * cos(deg2rad($angles[$i])),
$imcolor, $font, $label[$i]);
}


   return;
}

///////////////////////////////////////////////////////////////////////////
////////
///////////////////////////////////////////////////////////////////////////
///////



function array_trim ( $array, $index ) {
   if ( is_array ( $array ) ) {
     unset ( $array[$index] );
     array_unshift ( $array, array_shift ( $array ) );
     return $array;
     }
   else {
     return false;
     }
   }
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.osgeo.org/pipermail/mapserver-users/attachments/20060202/eec6b07b/attachment.html


More information about the mapserver-users mailing list