Anybody already implemented/compiled that? Docs? Samples?
ty
Date: Mon, 25 Apr 2005 10:50:38 -0500
Reply-To: James Sohr <[EMAIL PROTECTED]>
Sender: UMN MapServer Users List <[email protected]>
From:
James Sohr <[EMAIL PROTECTED]>
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.gif http://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"); [EMAIL PROTECTED]>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; } }
