Path-following labels function (MapScript)
Paul james
pauljame at GMAIL.COM
Thu Feb 2 07:23:10 PST 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.htm>
More information about the MapServer-users
mailing list