mapweaver/mwWayLabel.pm

436 lines
15 KiB
Perl

#
# PERL mapweaver module by gary68
#
#
#
#
# Copyright(C)2011, Gerhard Schwanz
#
# This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 3 of the License, or(at your option)any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with this program; if not, see <http://www.gnu.org/licenses/>
#
package mwWayLabel;
use strict;
use warnings;
use mwConfig;
use mwFile;
use mwMisc;
use mwMap;
use mwLabel;
use mwOccupy;
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
require Exporter;
@ISA = qw(Exporter AutoLoader);
@EXPORT = qw(addToDirectory
getDirectory
addWayLabel
preprocessWayLabels
createWayLabels
);
my %directory =();
my %wayLabels =();
my @labelCandidates =();
my %ruleRefs =();
my $pathNumber = 0;
my $numWayLabelsOmitted = 0;
my $wnsNumber = 1;
my @wns =();
# ------------------------------------------------------------------------
sub addToDirectory{ my($name, $square)= @_;
if(! defined $square){ $directory{$name}= 1;
} else{ $directory{$name}{$square}= 1;
}}
sub getDirectory{ return \%directory;
}
sub addWayLabel{#
# collect all way label data before actual labeling
#
my($wayId, $name, $ruleRef)= @_;
push @{$wayLabels{$ruleRef}{$name}}, $wayId;
$ruleRefs{$ruleRef}= $ruleRef;
if(cv('debug')eq "1"){ print "AWL: $wayId, $name, $ruleRef\n";
}}
sub preprocessWayLabels{#
# preprocess way labels collected so far
# combine ways with same rule and name
# split ways where direction in longitude changes so labels will be readable later
# store result in @labelCandidates
#
my($lonRef, $latRef)= getNodePointers();
my($memWayNodesRef, $memWayTagsRef)= getWayPointers();
foreach my $rule(keys %wayLabels){ my $ruleRef = $ruleRefs{$rule};
# print "PPWL: ruleNum $rule\n";
foreach my $name(keys %{$wayLabels{$rule}}){ my(@ways)= @{$wayLabels{$rule}{$name}};
# print "PPWL: processing name $name, " . scalar(@ways). " ways\n";
my($waysRef, $nodesRef)= buildRings(\@ways, 0);
my @segments = @$nodesRef;
# print "PPWL: processing name $name, " . scalar(@segments). " segments\n";
if(! grep /shield:/i, $name){
my @newSegments =();
foreach my $segment(@segments){ my @actual = @$segment;
# print "PPWL: Actual segment @actual\n";
my $found = 1;
while($found){ $found = 0; my $sp = 0;
# look for splitting point
LABSP: for(my $i=1; $i<$#actual; $i++){ if((($$lonRef{$actual[$i-1]}> $$lonRef{$actual[$i]})and($$lonRef{$actual[$i+1]}> $$lonRef{$actual[$i]}))or
(($$lonRef{$actual[$i-1]}< $$lonRef{$actual[$i]})and($$lonRef{$actual[$i+1]}< $$lonRef{$actual[$i]}))){ $found = 1;
$sp = $i;
last LABSP;
} } if($found == 1){ # print "\nname $name --- sp: $sp\n";
# print "ACTUAL BEFORE: @actual\n";
# create new seg
my @newSegment = @actual[0..$sp];
push @newSegments, [@newSegment];
# print "NEW: @newSegment\n";
# splice actual
splice @actual, 0, $sp;
# print "ACTUAL AFTER: @actual\n\n";
} } @$segment = @actual;
}
push @segments, @newSegments;
}
foreach my $segment(@segments){ my(@wayNodes)= @$segment;
my @points =();
if($$lonRef{$wayNodes[0]}> $$lonRef{$wayNodes[-1]}){ if((! grep /motorway/, $$ruleRef{'keyvalue'})and(! grep /trunk/, $$ruleRef{'keyvalue'})){ @wayNodes = reverse @wayNodes;
} }
foreach my $node(@wayNodes){ push @points, convert($$lonRef{$node}, $$latRef{$node});
} # print "PPWL: segment @wayNodes\n";
# print "PPWL: segment @points\n";
my($segmentLengthPixels)= 0;
for(my $i=0; $i<$#wayNodes; $i++){ my($x1, $y1)= convert($$lonRef{$wayNodes[$i]}, $$latRef{$wayNodes[$i]});
my($x2, $y2)= convert($$lonRef{$wayNodes[$i+1]}, $$latRef{$wayNodes[$i+1]});
$segmentLengthPixels += sqrt(($x2-$x1)**2 +($y2-$y1)**2);
} # print "$rule, $wayIndexLabelSize\n";
my $labelLengthPixels = 0;
if(grep /shield/i, $$ruleRef{'label'}){ $labelLengthPixels = $$ruleRef{'labelsize'};
# print "PPWL: len = $labelLengthPixels\n";
} else{ $labelLengthPixels = length($name)* cv('ppc')/ 10 * $$ruleRef{'labelsize'};
}
# print "\nPPWL: name $name - ppc $ppc - size $ruleArray[$wayIndexLabelSize]\n";
# print "PPWL: wayLen $segmentLengthPixels\n";
# print "PPWL: labLen $labelLengthPixels\n";
push @labelCandidates, [$rule, $name, $segmentLengthPixels, $labelLengthPixels, [@points]];
if(cv('debug')eq "1"){ print "PLC: $rule, $name, $segmentLengthPixels, $labelLengthPixels\n";
} } }}}
sub subWay{#
# takes coordinates and label information and creates new way/path
# also calculates total angles / bends
#
my($ref, $labLen, $alignment, $position)= @_;
my @coordinates = @$ref;
my @points;
my @dists;
my @angles =();
for(my $i=0; $i < $#coordinates; $i+=2){ push @points, [$coordinates[$i],$coordinates[$i+1]];
}
$dists[0] = 0;
my $dist = 0;
if(scalar @points > 1){ for(my $i=1;$i<=$#points; $i++){ $dist = $dist + sqrt(($points[$i-1]->[0]-$points[$i]->[0])**2 +($points[$i-1]->[1]-$points[$i]->[1])**2);
$dists[$i] = $dist;
}
}
# calc angles at nodes
if(scalar @points > 2){ for(my $i=1;$i<$#points; $i++){ $angles[$i] = angleMapgen($points[$i-1]->[0], $points[$i-1]->[1], $points[$i]->[0], $points[$i]->[1], $points[$i]->[0], $points[$i]->[1], $points[$i+1]->[0], $points[$i+1]->[1]);
}
}
my $wayLength = $dist;
my $refPoint = $wayLength / 100 * $position;
my $labelStart; my $labelEnd;
if($alignment eq "start"){# left
$labelStart = $refPoint;
$labelEnd = $labelStart + $labLen;
} if($alignment eq "end"){# right
$labelEnd = $refPoint;
$labelStart = $labelEnd - $labLen;
} if($alignment eq "middle"){# center
$labelEnd = $refPoint + $labLen / 2;
$labelStart = $refPoint - $labLen / 2;
}
# find start and end segments
my $startSeg; my $endSeg;
for(my $i=0; $i<$#points; $i++){ if(($dists[$i]<=$labelStart)and($dists[$i+1]>=$labelStart)){$startSeg = $i;} if(($dists[$i]<=$labelEnd)and($dists[$i+1]>=$labelEnd)){$endSeg = $i;}}
my @finalWay =();
my $finalAngle = 0;
my($sx, $sy)= triangleNode($coordinates[$startSeg*2], $coordinates[$startSeg*2+1], $coordinates[$startSeg*2+2], $coordinates[$startSeg*2+3], $labelStart-$dists[$startSeg], 0);
push @finalWay, $sx, $sy;
if($startSeg != $endSeg){ for(my $i=$startSeg+1; $i<=$endSeg; $i++){
push @finalWay, $coordinates[$i*2], $coordinates[$i*2+1];
$finalAngle += abs($angles[$i]);
}}
my($ex, $ey)= triangleNode($coordinates[$endSeg*2], $coordinates[$endSeg*2+1], $coordinates[$endSeg*2+2], $coordinates[$endSeg*2+3], $labelEnd-$dists[$endSeg], 0);
push @finalWay, $ex, $ey;
return(\@finalWay, $finalAngle);
}
sub createWayLabels{#
# finally take all way label candidates and try to label them
#
my %wnsUnique =();
print "placing way labels...\n";
my %notDrawnLabels =();
my %drawnLabels =();
# calc ratio to label ways first where label just fits
# these will be drawn first
foreach my $candidate(@labelCandidates){ my $wLen = $candidate->[2];
my $lLen = $candidate->[3];
if($wLen == 0){$wLen = 1;} if($lLen == 0){$lLen = 1;} $candidate->[5] = $lLen / $wLen;
} @labelCandidates = sort{$b->[5] <=> $a->[5]}@labelCandidates;
foreach my $candidate(@labelCandidates){ my $ruleRef = $ruleRefs{$candidate->[0]};
my $name = $candidate->[1];
my $wLen = $candidate->[2];
my $lLen = $candidate->[3];
my @points = @{$candidate->[4]};
my $toLabel = 1;
if((cv('declutter')eq "1")and($points[0] > $points[-2])and
((grep /motorway/i, $$ruleRef{'keyvalue'})or(grep /trunk/i, $$ruleRef{'keyvalue'}))){ $toLabel = 0;
}
# wns?
if(($lLen > $wLen * 0.95)and(cv('wns')> 0)){ if(($toLabel != 0)and(! grep /shield:/i, $name)and(wayVisible(\@points))){ if(! defined $wnsUnique{$name}){ my $oldName = $name;
$wnsUnique{$name}= 1;
push @wns, [ $wnsNumber, $name];
$name = $wnsNumber;
$lLen = cv('ppc')/ 10 * $$ruleRef{'labelsize'}* length($name);
# print "WNS: $oldName - $name\n";
$wnsNumber++;
} } }
if(($lLen > $wLen*0.95)or($toLabel == 0)){ # label too long
$numWayLabelsOmitted++;
$notDrawnLabels{$name}= 1;
} else{
if(grep /shield:/i, $name){
createShield($name, $$ruleRef{'labelsize'});
my $shieldMaxSize = getMaxShieldSize($name);
my $numShields = int($wLen /($shieldMaxSize * 12));
# if($numShields > 4){$numShields = 4;}
if($numShields > 0){ my $step = $wLen /($numShields + 1);
my $position = $step;
while($position < $wLen){ my($x, $y)= getPointOfWay(\@points, $position);
# print "XY: $x, $y\n";
if(! coordsOut($x, $y)){
# place shield if not occupied
my($ssx, $ssy)= getShieldSizes($name);
my $x2 = int($x - $ssx / 2);
my $y2 = int($y - $ssy / 2);
# print "AREA: $x2, $y2, $x2+$lLen, $y2+$lLen\n";
if(! mwLabel::boxAreaOccupied($x2, $y2+$ssy, $x2+$ssx, $y2)){
my $id = getShieldId($name);
addToLayer("shields", "<use xlink:href=\"#$id\" x=\"$x2\" y=\"$y2\" />");
mwLabel::boxOccupyArea($x2, $y2+$ssy, $x2+$ssx, $y2, 0, 3);
} }
$position += $step;
} }
}# shield
else{
# print "$wLen - $name - $lLen\n";
my $numLabels = int($wLen /(4 * $lLen));
if($numLabels < 1){$numLabels = 1;} if($numLabels > 4){$numLabels = 4;}
if($numLabels == 1){ # print "LA: $name *1*\n";
my $spare = 0.95 * $wLen - $lLen;
my $sparePercentHalf = $spare /($wLen*0.95)*100 / 2;
my $startOffset = 50 - $sparePercentHalf;
my $endOffset = 50 + $sparePercentHalf;
# five possible positions per way
my $step =($endOffset - $startOffset)/ 5;
my @positions =();
my $actual = $startOffset;
my $size = $$ruleRef{'labelsize'};
while($actual <= $endOffset){ my($ref, $angle)= subWay(\@points, $lLen, "middle", $actual);
my @way = @$ref;
# my($col)= lineCrossings(\@way);
my($col)= boxLinesOccupied(\@way, $size/2);
# calc quality of position. distance from middle and bend angles
my $quality = $angle + abs(50 - $actual);
if($col == 0){push @positions, ["middle", $actual, $quality];} $actual += $step;
} if(scalar @positions > 0){ $drawnLabels{$name}= 1;
# sort by quality and take best one
@positions = sort{$a->[2] <=> $b->[2]}@positions;
my($pos)= shift @positions;
my($ref, $angle)= subWay(\@points, $lLen, $pos->[0], $pos->[1]);
my @finalWay = @$ref;
# TODO IF INSIDE
# print "final way @finalWay\n";
if(! coordsOut(@finalWay)){ my $pathName = "Path" . $pathNumber; $pathNumber++;
createPath($pathName, \@finalWay, "definitions");
my $size = $$ruleRef{'labelsize'};
my $color = $$ruleRef{'labelcolor'};
my $font = $$ruleRef{'labelfont'};
my $fontFamily = $$ruleRef{'labelfontfamily'};
my $labelBold = $$ruleRef{'labelbold'};
my $labelItalic = $$ruleRef{'labelitalic'};
my $labelHalo = $$ruleRef{'labelhalo'};
my $labelHaloColor = $$ruleRef{'labelhalocolor'};
my $svgText = createTextSVG($fontFamily, $font, $labelBold, $labelItalic, $size, $color, $labelHalo, $labelHaloColor);
# pathText($svgText, $name, $pathName, $$ruleRef{'labeloffset'}, $pos->[0], $pos->[1], "text");
pathText($svgText, $name, $pathName, $$ruleRef{'labeloffset'}, $pos->[0], 50, "text");
boxOccupyLines(\@finalWay, $size/2, 3);
} } else{ $numWayLabelsOmitted++;
} } else{# more than one label
# print "LA: $name *X*\n";
my $labelDrawn = 0;
my $interval = int(100 /($numLabels + 1));
my @positions =();
for(my $i=1; $i<=$numLabels; $i++){ push @positions, $i * $interval;
}
foreach my $position(@positions){ my($refFinal, $angle)= subWay(\@points, $lLen, "middle", $position);
my(@finalWay)= @$refFinal;
# my($collision)= lineCrossings(\@finalWay);
my $size = $$ruleRef{'labelsize'};
my($collision)= boxLinesOccupied(\@finalWay, $size/2);
if($collision == 0){ $labelDrawn = 1;
$drawnLabels{$name}= 1;
my $pathName = "Path" . $pathNumber; $pathNumber++;
# createPath($pathName, \@points, "definitions");
createPath($pathName, \@finalWay, "definitions");
my $size = $$ruleRef{'labelsize'};
my $color = $$ruleRef{'labelcolor'};
my $font = $$ruleRef{'labelfont'};
my $fontFamily = $$ruleRef{'labelfontfamily'};
my $labelBold = $$ruleRef{'labelbold'};
my $labelItalic = $$ruleRef{'labelitalic'};
my $labelHalo = $$ruleRef{'labelhalo'};
my $labelHaloColor = $$ruleRef{'labelhalocolor'};
my $svgText = createTextSVG($fontFamily, $font, $labelBold, $labelItalic, $size, $color, $labelHalo, $labelHaloColor);
pathText($svgText, $name, $pathName, $$ruleRef{'labeloffset'}, "middle", 50, "text");
boxOccupyLines(\@finalWay, $size/2, 3);
} else{ # print "INFO: $name labeled less often than desired.\n";
} } if($labelDrawn == 0){ $notDrawnLabels{$name}= 1;
} } } }} my $labelFileName = cv('out');
$labelFileName =~ s/\.svg/_NotDrawnLabels.txt/;
my $labelFile;
open($labelFile, ">", $labelFileName)or die("couldn't open label file $labelFileName");
print $labelFile "Not drawn labels\n\n";
foreach my $labelName(sort keys %notDrawnLabels){ if(!defined $drawnLabels{$labelName}){ print $labelFile "$labelName\n";
}} close($labelFile);
# way name substitutes legend?
if(cv('wns')> 0){ createWNSLegend();
}
}
# ------------------------------------------------------------
sub createWNSLegend{ my $size = cv('wnssize');
my $color = cv('wnscolor');
# TODO max len auto size
my $maxLen = 0;
foreach my $e(@wns){ if(length $e->[1] > $maxLen){$maxLen = length $e->[1];}}
my $sy = 2 * $size;
my $sx =(4 + $maxLen)* $size / 10 * cv('ppc');
my $tx = 4 * $size / 10 * cv('ppc');
my $nx = 1 * $size / 10 * cv('ppc');
my $ty = 1.5 * $size;
my $sizeX = $sx;
my $sizeY = $sy * scalar @wns;
# defs
my $actualLine = 0;
addToLayer("definitions", "<g id=\"wnsdef\" width=\"$sizeX\" height=\"$sizeY\" >");
# bg
my $bg = cv('wnsbgcolor');
my $svgString = "fill=\"$bg\"";
drawRect(0, 0, $sizeX, $sizeY, 0, $svgString, "definitions");
$svgString = createTextSVG(cv('elementFontFamily'), cv('elementFont'), undef, undef, cv('wnssize'), cv('wnscolor'), undef, undef);
foreach my $e(@wns){ my $y = $actualLine * $sy + $ty;
drawText($nx, $y, 0, $e->[0], $svgString, "definitions");
drawText($tx, $y, 0, $e->[1], $svgString, "definitions");
$actualLine++;
}
addToLayer("definitions", "</g>");
my $posX = 0;
my $posY = 0;
# reset some variables
($sizeX, $sizeY)= getDimensions();
$sy = $sy * scalar @wns;
if(cv('wns')eq "2"){ $posX = $sizeX - $sx;
$posY = 0;
}
if(cv('wns')eq "3"){ $posX = 0;
$posY = $sizeY - $sy;
}
if(cv('wns')eq "4"){ $posX = $sizeX - $sx;
$posY = $sizeY - $sy;
}
if((cv('wns')>=1)and(cv('wns')<= 4)){ addToLayer("wns", "<use x=\"$posX\" y=\"$posY\" xlink:href=\"#wnsdef\" />");
}
if(cv('wns')eq "5"){ createLegendFile($sx, $sy, "_wns", "#wnsdef");
}}
1;