436 lines
15 KiB
Perl
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;
|
|
|
|
|