source: subversion/applications/rendering/tilesAtHome/tilesGen.pl @ 6933

Revision 6933, 60.9 KB checked in by deelkar, 6 years ago (diff)

add more debug infor

  • Property svn:executable set to *
  • Property svn:keywords set to Revision
Line 
1#!/usr/bin/perl
2use LWP::UserAgent;
3use Math::Trig;
4use File::Copy;
5use File::Temp qw(tempfile);
6use FindBin qw($Bin);
7use tahconfig;
8use tahlib;
9use tahproject;
10use English '-no_match_vars';
11use GD qw(:DEFAULT :cmp); 
12use strict;
13#-----------------------------------------------------------------------------
14# OpenStreetMap tiles@home
15#
16# Contact OJW on the Openstreetmap wiki for help using this program
17#-----------------------------------------------------------------------------
18# Copyright 2006, Oliver White, Etienne Cherdlu, Dirk-Lueder Kreie,
19# Sebastian Spaeth and others
20#
21# This program is free software; you can redistribute it and/or
22# modify it under the terms of the GNU General Public License
23# as published by the Free Software Foundation; either version 2
24# of the License, or (at your option) any later version.
25#
26# This program is distributed in the hope that it will be useful,
27# but WITHOUT ANY WARRANTY; without even the implied warranty of
28# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
29# GNU General Public License for more details.
30#
31# You should have received a copy of the GNU General Public License
32# along with this program; if not, write to the Free Software
33# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
34#-----------------------------------------------------------------------------
35# Read the config file
36my %Config = ReadConfig("tilesAtHome.conf", "general.conf", "authentication.conf", "layers.conf");
37my %EnvironmentInfo = CheckConfig(\%Config);
38
39my $Layers = $Config{"Layers"};
40
41# Get version number from version-control system, as integer
42my $Version = '$Revision$';
43$Version =~ s/\$Revision:\s*(\d+)\s*\$/$1/;
44printf STDERR "This is version %d (%s) of tilesgen running on %s\n", 
45    $Version, $Config{ClientVersion}, $^O;
46
47# check GD
48eval GD::Image->trueColor(1);
49if ($@ ne '') {
50    print STDERR "please update your libgd to version 2 for TrueColor support";
51    cleanUpAndDie("init:libGD check failed, exiting","EXIT",4,$PID);
52}
53
54# Setup GD options
55# currently unused (GD 2 truecolor mode)
56#
57#   my $numcolors = 256; # 256 is maximum for paletted output and should be used
58#   my $dither = 0; # dithering on or off.
59#
60# dithering off should try to find a good palette, looks ugly on
61# neighboring tiles with different map features as the "optimal"
62# palette is chosen differently for different tiles.
63
64# create a comparison blank image
65my $EmptyLandImage = new GD::Image(256,256);
66my $MapLandBackground = $EmptyLandImage->colorAllocate(248,248,248);
67$EmptyLandImage->fill(127,127,$MapLandBackground);
68
69my $EmptySeaImage = new GD::Image(256,256);
70my $MapSeaBackground = $EmptySeaImage->colorAllocate(181,214,241);
71$EmptySeaImage->fill(127,127,$MapSeaBackground);
72
73# Some broken versions of Inkscape occasionally produce totally black
74# output. We detect this case and throw an error when that happens.
75my $BlackTileImage = new GD::Image(256,256);
76my $BlackTileBackground = $BlackTileImage->colorAllocate(0,0,0);
77$BlackTileImage->fill(127,127,$BlackTileBackground);
78
79# set the progress indicator variables
80my $currentSubTask;
81my $progress = 0;
82my $progressJobs = 0;
83my $progressPercent = 0;
84
85# We need to keep parent PID so that child get the correct files after fork()
86my $parent_pid = $PID;
87my $upload_pid = -1;
88
89my $upload_result = 0;
90
91# Subdirectory for the current job (layer & z12 tileset),
92# as used in sub GenerateTileset() and tileFilename()
93my $JobDirectory;
94
95# keep track of time running
96my $progstart = time();
97my $dirent; 
98
99# keep track of the server time for current job
100my $JobTime;
101
102# Check the on disk image tiles havn't been corrupted.
103# these are flagfiles that tell the server certain metainfo through their filesize.
104if( -s "emptyland.png" != 67 )
105{
106    print STDERR "Corruption detected in emptyland.png. Trying to redownload from svn automatically.\n";
107    statusMessage("Downloading: emptyland.png", $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent,0);
108    DownloadFile(
109      "http://svn.openstreetmap.org/applications/rendering/tilesAtHome/emptyland.png", # TODO: should be svn update, instead of http get...
110      "emptyland.png",
111      0); ## 0=delete old file from disk first
112}
113if( -s "emptysea.png" != 69 )
114{
115    print STDERR "Corruption detected in emptysea.png. Trying to redownload from svn automatically.\n";
116    statusMessage("Downloading: emptysea.png", $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent,0);
117    DownloadFile(
118      "http://svn.openstreetmap.org/applications/rendering/tilesAtHome/emptysea.png", # TODO: should be svn update, instead of http get...
119      "emptysea.png",
120      0); ## 0=delete old file from disk first
121}
122# Check the on disk image tiles are now in order
123if( -s "emptyland.png" != 67 or
124    -s "emptysea.png" != 69 )
125{
126    print STDERR "\nAutomatic fix failed. Exiting.\n";
127    cleanUpAndDie("init:emptytile_template_check repair failed, exiting","EXIT",4,$PID);
128}
129# Check the stylesheets for corruption and out of dateness, but only in loop mode
130# The existance check is to attempt to determine we're on a UNIX-like system
131
132if( $ARGV[0] eq "loop" and -e "/dev/null" )
133{
134    if( qx(svn status osmarender/*.x[ms]l 2>/dev/null) ne "" )
135    {
136        print STDERR "Custom changes in osmarender stylesheets. Examine the following output to fix:\n";
137        system("svn status osmarender/*.x[ms]l");
138        cleanUpAndDie("init.osmarender_stylesheet_check repair failed","EXIT",4,$PID);
139    }
140}
141
142# Create the working directory if necessary
143mkdir $Config{WorkingDirectory} if(!-d $Config{WorkingDirectory});
144
145# Handle the command-line
146my $Mode = shift();
147
148## set all fault counters to 0;
149resetFault("fatal");
150resetFault("nodata");
151resetFault("nodataXAPI");
152resetFault("utf8");
153resetFault("inkscape");
154resetFault("renderer");
155resetFault("upload");
156
157if($Mode eq "xy")
158{
159    # ----------------------------------
160    # "xy" as first argument means you want to specify a tileset to render
161    # ----------------------------------
162    my $X = shift();
163    my $Y = shift();
164    if( not defined $X or not defined $Y )
165    { die "Must specify tile coordinates\n" }
166    my $Zoom = shift();
167    if(not defined $Zoom)
168    {
169       $Zoom = 12;
170       statusMessage(" *** No zoomlevel specified! Assuming z12 *** ", $Config{Verbose}, "warning", $progressJobs, $progressPercent,1);
171    }
172    GenerateTileset($X, $Y, $Zoom);
173}
174elsif ($Mode eq "loop") 
175{
176    # ----------------------------------
177    # Continuously process requests from server
178    # ----------------------------------
179
180    # if this is a re-exec, we want to capture some of our status
181    # information from the command line. this feature allows setting
182    # any numeric variable by specifying "variablename=value" on the
183    # command line after the keyword "reexec". Currently unsuitable
184    # for alphanumeric variables.
185   
186    if (shift() eq "reexec")
187    {
188        my $idleSeconds; my $idleFor;
189        while(my $evalstr = shift())
190        {
191            die unless $evalstr =~ /^[A-Za-z]+=\d+/;
192            eval('$'.$evalstr);
193            print STDERR "$evalstr\n" if ($Config{Verbose});
194        }
195        setIdle($idleSeconds, 1);
196        setIdle($idleFor, 0);
197    }
198
199    # this is the actual processing loop
200   
201    while(1) 
202    {
203        ## before we start (another) round of rendering we first check if something bad happened in the past.
204        if (getFault("fatal") > 0)
205        {
206            cleanUpAndDie("Fatal error occurred during loop, exiting","EXIT",1,$PID);
207        }
208        elsif (getFault("inkscape") > 5)
209        {
210            cleanUpAndDie("Five times inkscape failed, exiting","EXIT",1,$PID);
211        }
212        elsif (getFault("renderer") > 10)
213        {
214            cleanUpAndDie("rendering a tileset failed 10 times in a row, exiting","EXIT",1,$PID);
215        }
216        elsif (getFault("upload") > 5) 
217        {
218            cleanUpAndDie("Five times the upload failed, perhaps the server doesn't like us, exiting","EXIT",1,$PID);
219        }
220
221        my $sleepdelay = 1;
222        ## note: Timeouts are cumulative so if there are X timeouts from api and Y timeouts from XAPI then we wait for each timeout, one after the other
223        if (getFault("nodata") > 0) # check every network condition regardless of the other network outcomes
224        {
225            my $numfaults=getFault("nodata");
226            if ($numfaults > 5)
227            {
228                cleanUpAndDie("More than five times no data, perhaps the server doesn't like us, exiting","EXIT",1,$PID);
229            }
230            else
231            {
232                $sleepdelay=16*(4**$numfaults); # wait 64, 256, 1024, 4096, 16384 seconds. for a total of about 6 hours
233                $sleepdelay=int($sleepdelay)+1;
234                talkInSleep($numfaults." times no data", $sleepdelay);
235            }
236        }
237        if (getFault("nodataXAPI") > 0)
238        {
239            my $numfaults=getFault("nodataXAPI");
240            if ($numfaults >= 10)
241            {
242                cleanUpAndDie("10 times no data from XAPI, perhaps the server doesn't like us, exiting","EXIT",1,$PID); # allow XAPI more leeway
243            }
244            else
245            {
246                $sleepdelay=16*(2**$numfaults); # wait 32, 64, 128, 256, 512, 1024, 4096, 8192 seconds for a total of about 4 hours
247                $sleepdelay=int($sleepdelay)+1;
248                talkInSleep($numfaults." times no XAPI data", $sleepdelay);
249            }
250        }
251        # look for stopfile and exit if found
252        if (-e "stopfile.txt")
253        {
254            if ($Config{"ForkForUpload"} && $upload_pid != -1)
255            {
256                statusMessage("Waiting for previous upload process", $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent,0);
257                waitpid($upload_pid, 0);
258            }
259            cleanUpAndDie("Stopfile found, exiting","EXIT",7,$PID); ## TODO: agree on an exit code scheme for different types of errors
260        }
261
262        reExecIfRequired(); ## check for new version of tilesGen.pl and reExec if true
263
264        my ($did_something, $message) = ProcessRequestsFromServer(); # Actually render stuff if job on server
265       
266        $upload_result = uploadIfEnoughTiles(); # upload if enough work done
267       
268        if ($upload_result)  # we got an error in the upload process
269        {
270              addFault("upload",1); # we only track errors that occur multple times in a row
271        }
272        else
273        {
274              resetFault("upload"); #reset fault counter for uploads if once without error
275        }
276
277        if ($did_something == 0) 
278        {
279            talkInSleep($message, 60);
280        }
281        else
282        {
283            setIdle(0,0);
284        }
285    }
286}
287elsif ($Mode eq "upload") 
288{
289    $currentSubTask = "warning";
290    statusMessage("don't run this parallel to another tilesGen.pl instance",$Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent,1);
291    compressAndUpload();
292}
293elsif ($Mode eq "upload_conditional") 
294{
295    $currentSubTask = "warning";
296    statusMessage("don't run this parallel to another tilesGen.pl instance",$Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent,1);
297    uploadIfEnoughTiles();
298}
299elsif ($Mode eq "version") 
300{
301    exit(1);
302}
303elsif ($Mode eq "") 
304{
305    # ----------------------------------
306    # Normal mode downloads request from server
307    # ----------------------------------
308    my ($did_something, $message) = ProcessRequestsFromServer();
309   
310    if (! $did_something)
311    {
312        statusMessage("you may safely press Ctrl-C now if you ran this as \"tilesGen.pl\" from the command line.", $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent,1);
313        talkInSleep($message, 60);
314    }
315    statusMessage("if you want to run this program continuously, use loop mode", $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent,1);
316}
317else
318{
319    # ----------------------------------
320    # "help" (or any other non understood parameter) as first argument tells how to use the program
321    # ----------------------------------
322    my $Bar = "-" x 78;
323    print "\n$Bar\nOpenStreetMap tiles\@home client\n$Bar\n";
324    print "Usage: \nNormal mode:\n  \"$0\", will download requests from server\n";
325    print "Specific area:\n  \"$0 xy <x> <y> [z]\"\n  (x and y coordinates of a zoom-12 (default) tile in the slippy-map coordinate system)\n  See [[Slippy Map Tilenames]] on wiki.openstreetmap.org for details\nz is optional and can be used for low-zoom tilesets\n";
326    print "Other modes:\n";
327    print "  $0 loop - runs continuously\n";
328    print "  $0 upload - uploads any tiles\n";
329    print "  $0 upload_conditional - uploads tiles if there are many waiting\n";
330    print "  $0 version - prints out version string and exits\n";
331    print "\nGNU General Public license, version 2 or later\n$Bar\n";
332}
333
334sub uploadIfEnoughTiles
335{
336    my $Count = 0;
337    my $ZipCount = 0;
338
339    # compile a list of the "Prefix" values of all configured layers,
340    # separated by |
341    my $allowedPrefixes = join("|", 
342        map($Config{"Layer.$_.Prefix"}, split(/,/,$Layers)));
343
344    if (opendir(my $dp, $Config{WorkingDirectory}))
345    {
346        while(my $File = readdir($dp))
347        {
348            $Count++ if ($File =~ /($allowedPrefixes)_.*\.png/);
349            $Count += 200 if ($File =~ /($allowedPrefixes)_.*\.dir/);
350        }
351        closedir($dp);
352    } 
353    else
354    {
355        mkdir $Config{WorkingDirectory};
356    }
357
358    if (opendir(my $dp, $Config{WorkingDirectory}."uploadable"))
359    {
360        while(my $File = readdir($dp))
361        {
362            $ZipCount++ if ($File =~ /\.zip$/);
363        }
364        closedir($dp);
365    }
366    else 
367    {
368        mkdir $Config{WorkingDirectory}."uploadable";
369    }
370
371    if (($Count >= 200) or ($ZipCount >= 1))
372    {
373        if ($Config{"ForkForUpload"} and ($Mode eq "loop")) # makes no sense to fork upload if not looping.
374        {
375            # Upload is handled by another process, so that we can generate another tile at the same time.
376            # We still don't want to have two uploading process running at the same time, so we wait for the previous one to finish.
377            if ($upload_pid != -1)
378            {
379                statusMessage("Waiting for previous upload process to finish", $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent,0);
380                waitpid($upload_pid, 0);
381                $upload_result = $? >> 8;
382            }
383            compress(1); #compress before fork so we don't get temp files mangled. Workaround for batik support.
384            $upload_pid = fork();
385            if ((not defined $upload_pid) or ($upload_pid == -1))
386            {
387                cleanUpAndDie("loop: could not fork, exiting","EXIT",4,$PID); # exit if asked to fork but unable to
388            }
389            elsif ($upload_pid == 0)
390            {
391                ## we are the child, so we run the upload
392                my $res = upload(1); # upload if enough work done
393                exit($res);
394            }
395        }
396        else
397        {
398            ## no forking going on
399            return compressAndUpload();
400        }
401    }
402    else
403    {
404        print "Not uploading yet, only $Count tiles\n"  if ($Config{"Verbose"});
405    }
406}
407
408sub compressAndUpload
409{
410  my $error=0;
411  $error=compress(1) + 2 * upload(1) + 4 * compress(2) + 8 * upload(2);
412  return $error;
413}
414
415sub compress
416{
417    ## Run compress directly because it uses same messaging as tilesGen.pl and upload.pl
418    ## no need to hide output at all.
419
420    my ($runNumber) = @_;
421
422    my $CompressScript = "perl $Bin/compress.pl $runNumber $progressJobs";
423    my $retval = system($CompressScript);
424    return $retval;
425}
426
427sub upload
428{
429    ## Run upload directly because it uses same messaging as tilesGen.pl,
430    ## no need to hide output at all.
431
432    my ($runNumber) = @_;
433
434    my $UploadScript = "perl $Bin/upload.pl $runNumber $progressJobs";
435    my $retval = system($UploadScript);
436    return $retval;
437}
438
439#-----------------------------------------------------------------------------
440# Ask the server what tileset needs rendering next
441#-----------------------------------------------------------------------------
442sub ProcessRequestsFromServer 
443{
444    if ($Config{"LocalSlippymap"})
445    {
446        print "Config option LocalSlippymap is set. Downloading requests\n";
447        print "from the server in this mode would take them from the tiles\@home\n";
448        print "queue and never upload the results. Program aborted.\n";
449        cleanUpAndDie("ProcessRequestFromServer:LocalSlippymap set, exiting","EXIT",1,$PID);
450    }
451   
452    my $ValidFlag;
453    my $Version;
454    my $X;
455    my $Y;
456    my $Z;
457    my $ModuleName;
458   
459    # ----------------------------------
460    # Download the request, and check it
461    # Note: to find out exactly what this server is telling you,
462    # add ?help to the end of the URL and view it in a browser.
463    # It will give you details of other help pages available,
464    # such as the list of fields that it's sending out in requests
465    # ----------------------------------
466
467    my $Request = GetRequestFromServer($Config{RequestMethod});
468
469    return (0, "Error reading request from server") unless ($Request);
470
471    ($ValidFlag,$Version) = split(/\|/, $Request);
472
473    # Check what format the results were in
474    # If you get this message, please do check for a new version, rather than
475    # commenting-out the test - it means the field order has changed and this
476    # program no longer makes sense!
477    if (($Version < 3) or ($Version > 4))
478    {
479        print STDERR "\n";
480        print STDERR "Server is speaking a different version of the protocol to us.\n";
481        print STDERR "Check to see whether a new version of this program was released!\n";
482        cleanUpAndDie("ProcessRequestFromServer:Request API version mismatch, exiting \n".$Request,"EXIT",1,$PID);
483        ## No need to return, we exit the program at this point
484    }
485   
486    # Parse the request
487    if ($Version == 3)
488    {
489        ($ValidFlag,$Version,$X,$Y,$Z,$ModuleName) = split(/\|/, $Request);
490    }
491    elsif ($Version == 4)
492    {
493        ($ValidFlag,$Version,$X,$Y,$Z,$Layers,$ModuleName) = split(/\|/, $Request);
494    }
495    else
496    {
497        die "Version is \"".$Version."\". This should not have happened.";
498    }
499   
500    # First field is always "OK" if the server has actually sent a request
501    if ($ValidFlag eq "XX")
502    {
503        if ($Request =~ /Invalid username/)
504        {
505            die "ERROR: Authentication failed - please check your username "
506                    . "and password in 'authentication.conf'.";
507        }
508        else
509        {
510            # unspecified error
511            return (0, "Server has no work for us"); 
512        }
513    }
514    elsif ($ValidFlag ne "OK")
515    {
516        return (0, "Server dysfunctional");
517    }
518   
519    # Information text to say what's happening
520    statusMessage("Got work from the \"$ModuleName\" server module", $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent,0);
521   
522    # Create the tileset requested
523    GenerateTileset($X, $Y, $Z);
524    return (1, "");
525}
526
527
528# actually get the request from the server
529sub GetRequestFromServer
530{
531    my $RequestMethod=shift();
532    my $LocalFilename = $Config{WorkingDirectory} . "request-" . $PID . ".txt";
533    killafile($LocalFilename); ## make sure no old request file is laying around.
534
535    my $Request;
536
537    if ($RequestMethod eq "POST")
538    {
539        my $URL = $Config{RequestURL}."Request2.php"."?v=".$Config{ClientVersion}."&usr=".$Config{UploadUsername};
540   
541        my $ua = LWP::UserAgent->new(keep_alive => 1, timeout => 360);
542
543        $ua->protocols_allowed( ['http'] );
544        $ua->agent("tilesAtHome");
545        $ua->env_proxy();
546        push @{ $ua->requests_redirectable }, 'POST';
547
548        my $res = $ua->post($URL,
549          Content_Type => 'form-data',
550          Content => [ user => $Config{UploadUsername},
551          pass => $Config{UploadPassword},
552          version => $Config{ClientVersion},
553          layers => $Layers,
554          layerspossible => $Config{LayersCapability} ]);
555     
556        if(!$res->is_success())
557        {
558            print $res->content if ($Config{Debug});
559            return 0;
560        }
561        else
562        {
563            print $res->content if ($Config{Debug});
564            $Request = $res->content;  ## FIXME: check single line returned. grep?
565            chomp $Request;
566        }
567
568    }
569    else
570    {
571        return 0;
572    }
573    return $Request;
574}
575
576#-----------------------------------------------------------------------------
577# this is called when the client encounters errors in processing a tileset,
578# it's designed to tell the server the tileset will not be returned because
579# of said error
580#-----------------------------------------------------------------------------
581sub PutRequestBackToServer 
582{
583    ## TODO: will not be called in some libGD abort situations
584    my ($X,$Y,$Z,$Cause) = @_;
585
586    ## do not do this if called in xy mode!
587    return if($Mode eq "xy");
588   
589    my $Prio = $Config{ReRequestPrio};
590   
591    my $LocalFilename = $Config{WorkingDirectory} . "requesting-" . $PID . ".txt";
592   
593    killafile($LocalFilename); # maybe not necessary if DownloadFile is called correctly?
594   
595    my $RequestUrlString = $Config{ReRequestURL} . "?x=" . $X . "&y=" . $Y . "&z=" . $Z . "&priority=" . $Prio . "&src=" . $Config{UploadUsername}. ":tahCltReReq:" . $Cause;
596   
597    statusMessage("Putting Job ".$X.",".$Y." back to server", $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent,1);
598    DownloadFile(
599        $RequestUrlString, 
600        $LocalFilename, 
601        0);
602   
603    if(! -f $LocalFilename)
604    {
605        return (0, "Error reading response from server");
606    }
607   
608    # Read into memory
609    open(my $fp, "<", $LocalFilename) || return;
610    my $Request = <$fp>;
611    chomp $Request;
612    close $fp;
613   
614    ## TODO: Check response for "OK" or "Duplicate Entry" (which would be OK, too)
615   
616    killafile($LocalFilename); # don't leave old files laying around
617
618}
619
620#-----------------------------------------------------------------------------
621# Render a tile (and all subtiles, down to a certain depth)
622#-----------------------------------------------------------------------------
623sub GenerateTileset ## TODO: split some subprocesses to own subs
624{
625    my ($X, $Y, $Zoom) = @_;
626   
627    my ($N, $S) = Project($Y, $Zoom);
628    my ($W, $E) = ProjectL($X, $Zoom);
629   
630    $progress = 0;
631    $progressPercent = 0;
632    $progressJobs++;
633    $currentSubTask = "jobinit";
634   
635    statusMessage(sprintf("Doing tileset $X,$Y (zoom $Zoom) (area around %f,%f)", ($N+$S)/2, ($W+$E)/2), $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent, 1);
636   
637    my $maxCoords = (2 ** $Zoom - 1);
638   
639    if ( ($X < 0) or ($X > $maxCoords) or ($Y < 0) or ($Y > $maxCoords) )
640    {
641        #maybe do something else here
642        die("\n Coordinates out of bounds (0..$maxCoords)\n");
643    }
644   
645    $currentSubTask = "Preproc";
646   
647    # Adjust requested area to avoid boundary conditions
648    my $N1 = $N + ($N-$S)*$Config{BorderN};
649    my $S1 = $S - ($N-$S)*$Config{BorderS};
650    my $E1 = $E + ($E-$W)*$Config{BorderE};
651    my $W1 = $W - ($E-$W)*$Config{BorderW};
652
653    # TODO: verify the current system cannot handle segments/ways crossing the
654    # 180/-180 deg meridian and implement proper handling of this case, until
655    # then use this workaround:
656
657    if($W1 <= -180) {
658      $W1 = -180; # api apparently can handle -180
659    }
660    if($E1 > 180) {
661      $E1 = 180;
662    }
663
664    my $bbox = sprintf("%f,%f,%f,%f",
665      $W1, $S1, $E1, $N1);
666
667    #------------------------------------------------------
668    # Download data
669    #------------------------------------------------------
670    my $DataFile = $Config{"WorkingDirectory"}."data-$PID.osm";
671   
672    killafile($DataFile);
673    my $URLS = sprintf("%s%s/map?bbox=%s",
674      $Config{APIURL},$Config{OSMVersion},$bbox);
675    if ($Zoom < 12) 
676    {
677        # FIXME: hardcoded: assume lowzoom layer now!
678        $Layers="lowzoom" if ($Mode eq "xy");
679       
680        # We only need the bounding box for ways (they will be downloaded completly,
681        # but need the extended bounding box for places (names from neighbouring tiles)
682        $URLS = sprintf("%s%s/way[natural=*][bbox=%s] %s%s/way[boundary=*][bbox=%s] %s%s/way[landuse=*][bbox=%s] %s%s/way[highway=motorway|motorway_link|trunk|primary|secondary|tertiary][bbox=%s] %s%s/way[waterway=river][bbox=%s] %s%s/way[railway=*][bbox=%s] %s%s/node[place=*][bbox=%s]",
683          $Config{XAPIURL},$Config{OSMVersion},$bbox,
684          $Config{XAPIURL},$Config{OSMVersion},$bbox,
685          $Config{XAPIURL},$Config{OSMVersion},$bbox,
686          $Config{XAPIURL},$Config{OSMVersion},$bbox,
687          $Config{XAPIURL},$Config{OSMVersion},$bbox,
688          $Config{XAPIURL},$Config{OSMVersion},$bbox,
689          $Config{XAPIURL},$Config{OSMVersion},$bbox);
690    }
691    my @tempfiles;
692    push(@tempfiles, $DataFile);
693    my $filelist = [];
694    my $i=0;
695    foreach my $URL (split(/ /,$URLS)) 
696    {
697        ++$i;
698        my $partialFile = $Config{"WorkingDirectory"}."data-$PID-$i.osm";
699        push(@{$filelist}, $partialFile);
700        push(@tempfiles, $partialFile);
701        statusMessage("Downloading: Map data for $Layers to $partialFile", $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent,0);
702        print "Download $URL\n" if ($Config{Debug});
703        DownloadFile($URL, $partialFile, 0);
704
705        if (-s $partialFile == 0)
706        {
707            if ($Zoom < 12)
708            {
709                statusMessage("No data here...",$Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent, 1);
710                # if loop was requested just return  or else exit with an error.
711                # (to enable wrappers to better handle this situation
712                # i.e. tell the server the job hasn't been done yet)
713                PutRequestBackToServer($X,$Y,$Zoom,"NoData");
714                foreach my $file(@tempfiles) { killafile($file); }
715                addFault("nodataXAPI",1);
716                return cleanUpAndDie("GenerateTileset: no data!",$Mode,1,$PID);
717            }
718            else
719            {
720                statusMessage("No data here, trying smaller slices",$Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent, 1);
721                my $slice=(($E1-$W1)/10); # A chunk is one tenth of the width
722                for (my $j = 1 ; $j<=10 ; $j++)
723                {
724                    $URL = sprintf("%s%s/map?bbox=%f,%f,%f,%f", 
725                      $Config{APIURL},$Config{OSMVersion}, ($W1+($slice*($j-1))), $S1, ($W1+($slice*$j)), $N1); 
726                    $partialFile = $Config{"WorkingDirectory"}."data-$PID-$i-$j.osm";
727                    push(@{$filelist}, $partialFile);
728                    push(@tempfiles, $partialFile);
729                    statusMessage("Downloading: Map data to $partialFile (slice $j of 10)", $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent,0);
730                    DownloadFile($URL, $partialFile, 0);
731
732                    if (-s $partialFile == 0)
733                    {
734                        statusMessage("No data here (sliced)...",$Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent, 1);
735                        PutRequestBackToServer($X,$Y,$Zoom,"NoData");
736                        foreach my $file(@tempfiles) { killafile($file); }
737                        addFault("nodata",1);
738                        return cleanUpAndDie("GenerateTilesetSliced, no data (sliced).",$Mode,1,$PID);
739                    }
740                }
741                print STDERR "\n";
742            }
743        }
744        else
745        {
746            if ($Zoom < 12)
747            {
748                resetFault("nodataXAPI"); #reset to zero if data downloaded
749            }
750            else 
751            {
752                resetFault("nodata"); #reset to zero if data downloaded
753            }
754        }
755    }
756
757    mergeOsmFiles($DataFile, $filelist);
758
759    if ($Config{KeepDataFile})
760    {
761        copy($DataFile, $Config{WorkingDirectory} . "/" . "data.osm");
762    }
763 
764    # Get the server time for the data so we can assign it to the generated image (for tracking from when a tile actually is)
765    $JobTime = [stat $DataFile]->[9];
766   
767    # Check for correct UTF8 (else inkscape will run amok later)
768    # FIXME: This doesn't seem to catch all string errors that inkscape trips over.
769    statusMessage("Checking for UTF-8 errors in $DataFile", $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent, 0);
770    open(OSMDATA, $DataFile) || die ("could not open $DataFile for UTF-8 check");
771    my @toCheck = <OSMDATA>;
772    close(OSMDATA);
773    while (my $osmline = shift @toCheck)
774    {
775      if (utf8::is_utf8($osmline)) # this might require perl 5.8.1 or an explicit use statement
776      {
777        statusMessage("found incorrect UTF-8 chars in $DataFile, job $X $Y  $Zoom", $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent, 1);
778        PutRequestBackToServer($X,$Y,$Zoom,"BadUTF8");
779        addFault("utf8",1);
780        return cleanUpAndDie("GenerateTileset:UTF8 test failed",$Mode,1,$PID);
781      }
782    }
783    resetFault("utf8"); #reset to zero if no UTF8 errors found.
784    #------------------------------------------------------
785    # Handle all layers, one after the other
786    #------------------------------------------------------
787
788    foreach my $layer(split(/,/, $Layers))
789    {
790        #reset progress for each layer
791        $progress=0;
792        $progressPercent=0;
793        $currentSubTask = $layer;
794       
795        $JobDirectory = sprintf("%s%s_%d_%d_%d.tmpdir",
796                                $Config{WorkingDirectory},
797                                $Config{"Layer.$layer.Prefix"},
798                                $Zoom, $X, $Y);
799        mkdir $JobDirectory unless -d $JobDirectory;
800
801        my $maxzoom = $Config{"Layer.$layer.MaxZoom"};
802        my $layerDataFile;
803
804        # Faff around
805        for (my $i = $Zoom ; $i <= $maxzoom ; $i++) 
806        {
807            killafile($Config{WorkingDirectory}."output-$parent_pid-z$i.svg");
808        }
809       
810        my $Margin = " " x ($Zoom - 8);
811        #printf "%03d %s%d,%d: %1.2f - %1.2f, %1.2f - %1.2f\n", $Zoom, $Margin, $X, $Y, $S,$N, $W,$E;
812       
813       
814        #------------------------------------------------------
815        # Go through preprocessing steps for the current layer
816        #------------------------------------------------------
817        my @ppchain = ($PID);
818        # config option may be empty, or a comma separated list of preprocessors
819        foreach my $preprocessor(split /,/, $Config{"Layer.$layer.Preprocessor"})
820        {
821            my $inputFile = sprintf("%sdata-%s.osm", 
822                $Config{"WorkingDirectory"},
823                join("-", @ppchain));
824            push(@ppchain, $preprocessor);
825            my $outputFile = sprintf("%sdata-%s.osm", 
826                $Config{"WorkingDirectory"},
827                join("-", @ppchain));
828
829            if (-f $outputFile)
830            {
831                # no action; files for this preprocessing step seem to have been created
832                # by another layer already!
833            }
834            elsif ($preprocessor eq "maplint")
835            {
836                # Pre-process the data file using maplint
837                # TODO may put this into a subroutine of its own
838                my $Cmd = sprintf("%s \"%s\" tr %s %s > \"%s\"",
839                        $Config{Niceness},
840                        $Config{XmlStarlet},
841                        "maplint/lib/run-tests.xsl",
842                        "$inputFile",
843                        "tmp.$PID");
844                statusMessage("Running maplint", $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent,0);
845                runCommand($Cmd,$PID);
846                $Cmd = sprintf("%s \"%s\" tr %s %s > \"%s\"",
847                        $Config{Niceness},
848                        $Config{XmlStarlet},
849                        "maplint/lib/convert-to-tags.xsl",
850                        "tmp.$PID",
851                        "$outputFile");
852                statusMessage("Creating tags from maplint", $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent,0);
853                runCommand($Cmd,$PID);
854                killafile("tmp.$PID");
855            }
856            elsif ($preprocessor eq "close-areas")
857            {
858                my $Cmd = sprintf("%s perl close-areas.pl $X $Y $Zoom < %s > %s",
859                        $Config{Niceness},
860                        "$inputFile",
861                        "$outputFile");
862                statusMessage("Running close-areas", $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent,0);
863                runCommand($Cmd,$PID);
864            }
865            elsif ($preprocessor eq "attribution")
866            {
867                my $Cmd = sprintf("%s perl attribution.pl < %s > %s",
868                        $Config{Niceness},
869                        "$inputFile",
870                        "$outputFile");
871                statusMessage("Running attribution", $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent,0);
872                runCommand($Cmd,$PID);
873            }
874            elsif ($preprocessor eq "mercator")
875            {
876                my $Cmd = sprintf("%s perl mercatorize.pl %s > %s",
877                        $Config{Niceness},
878                        "$inputFile",
879                        "$outputFile");
880                statusMessage("Running Mercatorization", $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent,0);
881                runCommand($Cmd,$PID);
882            }
883            else
884            {
885                die "Invalid preprocessing step '$preprocessor'";
886            }
887## Uncomment to have the output files checked for validity
888#            if( $preprocessor ne "maplint" )
889#            {
890#              runCommand( qq(xmllint --dtdvalid http://dev.openstreetmap.org/~kleptog/tilesAtHome-0.3.dtd --noout $outputFile), $PID );
891#            }
892            push(@tempfiles, $outputFile);
893        }
894
895        #------------------------------------------------------
896        # Preprocessing finished, start rendering
897        #------------------------------------------------------
898
899        #$layerDataFile = sprintf("%sdata-%s.osm", $Config{"WorkingDirectory"}, join("-", @ppchain));
900        $layerDataFile = sprintf("data-%s.osm", join("-", @ppchain)); # Don't put working directory here, the path is relative to the rulesfile
901       
902        # Add bounding box to osmarender
903        # then set the data source
904        # then transform it to SVG
905        if ($Config{Fork}) 
906        {
907            my $minimum_zoom = $Zoom;
908            my $increment = 2 * $Config{Fork};
909            my @children_pid;
910            my $error = 0;
911            for (my $i = 0; $i < 2 * $Config{Fork} - 1; $i ++) 
912            {
913                my $pid = fork();
914                if (not defined $pid) 
915                {
916                    cleanUpAndDie("GenerateTileset: could not fork, exiting","EXIT",4,$PID); # exit if asked to fork but unable to
917                }
918                elsif ($pid == 0) 
919                {
920                    for (my $i = $minimum_zoom ; $i <= $maxzoom; $i += $increment) 
921                    {
922                        if (GenerateSVG($layerDataFile, $layer, $X, $Y, $i, $N, $S, $W, $E)) # if true then error occured
923                        {
924                             exit(1);
925                        }
926                    }
927                    exit(0);
928                }
929                else
930                {
931                    push(@children_pid, $pid);
932                    $minimum_zoom ++;
933                }
934            }
935            for (my $i = $minimum_zoom ; $i <= $maxzoom; $i += $increment) 
936            {
937                if (GenerateSVG($layerDataFile, $layer, $X, $Y, $i, $N, $S, $W, $E))
938                {
939                    $error = 1;
940                    last;
941                }
942            }
943            foreach (@children_pid) 
944            {
945                waitpid($_, 0);
946                $error |= $?;
947            }
948            if ($error) 
949            {
950                foreach my $file(@tempfiles) { killafile($file) if (!$Config{Debug}); }
951                return 0;
952            }
953        }
954        else
955        {
956            for (my $i = $Zoom ; $i <= $maxzoom; $i++)
957            {
958                if (GenerateSVG($layerDataFile, $layer, $X, $Y, $i, $N, $S, $W, $E))
959                {
960                    foreach my $file(@tempfiles) { killafile($file) if (!$Config{Debug}); }
961                    return 0;
962                }
963            }
964        }
965       
966        # Find the size of the SVG file
967        my ($ImgH,$ImgW,$Valid) = getSize($Config{WorkingDirectory}."output-$parent_pid-z$maxzoom.svg");
968
969        # Render it as loads of recursive tiles
970        my ($success,$empty) = RenderTile($layer, $X, $Y, $Y, $Zoom, $Zoom, $N, $S, $W, $E, 0,0,$ImgW,$ImgH,$ImgH,0);
971        if (!$success)
972        {
973            addFault("renderer",1);
974            return cleanUpAndDie("GenerateTileset: could not render tileset",$Mode,1,$PID);
975        }
976        else
977        {
978            resetFault("renderer");
979        }
980        # Clean-up the SVG files
981        for (my $i = $Zoom ; $i <= $maxzoom; $i++) 
982        {
983            killafile($Config{WorkingDirectory}."output-$parent_pid-z$i.svg") if (!$Config{Debug});
984        }
985
986        #if $empty then the next zoom level was empty, so we only upload one tile unless RenderFullTileset is set.
987        if ($empty == 1 && $Config{GatherBlankTiles}) 
988        {
989            my $Filename=sprintf("%s_%s_%s_%s.png",$Config{"Layer.$layer.Prefix"}, $Zoom, $X, $Y);
990            my $oldFilename = sprintf("%s/%s",$JobDirectory, $Filename); 
991            my $newFilename = sprintf("%s%s",$Config{WorkingDirectory},$Filename);
992            rename($oldFilename, $newFilename);
993            rmdir($JobDirectory);
994        }
995        else
996        {
997            # This directory is now ready for upload.
998            # How should errors in renaming be handled?
999            my $Dir = $JobDirectory;
1000            $Dir =~ s|\.tmpdir|.dir|;
1001            rename $JobDirectory, $Dir;
1002        }
1003
1004        if ($Config{LayerUpload}) 
1005        {
1006            uploadIfEnoughTiles();
1007        }
1008    }
1009
1010    foreach my $file(@tempfiles) { killafile($file) if (!$Config{Debug}); }
1011    return 1;
1012}
1013
1014#-----------------------------------------------------------------------------
1015# Generate SVG for one zoom level
1016#   $layerDataFile - name of the OSM data file
1017#   $X, $Y - which tileset (Always the tilenumbers of the base zoom. i.e. z12)
1018#   $Zoom - which zoom currently is processsed
1019#   $N, $S, $W, $E - bounds of the tile
1020#-----------------------------------------------------------------------------
1021sub GenerateSVG 
1022{
1023    my ($layerDataFile, $layer, $X, $Y, $Zoom, $N, $S, $W, $E) = @_;
1024    # Create a new copy of rules file to allow background update
1025    # don't need layer in name of file as we'll
1026    # process one layer after the other
1027    my $error = 0;
1028    my $source = $Config{"Layer.$layer.Rules.$Zoom"};
1029    my $TempFeatures = $Config{"WorkingDirectory"}."map-features-$PID-z$Zoom.xml";
1030    copy($source, $TempFeatures)
1031        or die "Cannot make copy of $source";
1032
1033    # Update the rules file  with details of what to do (where to get data, what bounds to use)
1034    AddBounds($TempFeatures,$W,$S,$E,$N);
1035    SetDataSource($layerDataFile, $TempFeatures);
1036
1037    # Render the file
1038    if (! xml2svg(
1039            $TempFeatures,
1040            $Config{WorkingDirectory}."output-$parent_pid-z$Zoom.svg",
1041            $Zoom))
1042    {
1043        $error = 1;
1044    }
1045    # Delete temporary rules file
1046    killafile($TempFeatures) if (! $Config{"Debug"});
1047    return $error;
1048}
1049
1050#-----------------------------------------------------------------------------
1051# Render a tile
1052#   $X, $Y - which tileset (Always the tilenumbers at $ZOrig)
1053#   $Ytile, $Zoom - which tilestripe
1054#   $ZOrig, the lowest zoom level which called tileset generation (i.e. z12 for "normal" operation)
1055#   $N, $S, $W, $E - bounds of the tile
1056#   $ImgX1,$ImgY1,$ImgX2,$ImgY2 - location of the tile in the SVG file
1057#   $ImageHeight - Height of the entire SVG in SVG units
1058#   $empty - put forward "empty" tilestripe information.
1059#-----------------------------------------------------------------------------
1060sub RenderTile 
1061{
1062    my ($layer, $X, $Y, $Ytile, $Zoom, $ZOrig, $N, $S, $W, $E, $ImgX1,$ImgY1,$ImgX2,$ImgY2,$ImageHeight,$SkipEmpty) = @_;
1063
1064    return (1,1) if($Zoom > $Config{"Layer.$layer.MaxZoom"});
1065   
1066    # no need to render subtiles if empty
1067    return (1,$SkipEmpty) if($SkipEmpty == 1);
1068
1069    # Render it to PNG
1070    printf "Tilestripe %s (%s,%s): Lat %1.3f,%1.3f, Long %1.3f,%1.3f, X %1.1f,%1.1f, Y %1.1f,%1.1f\n",       $Ytile,$X,$Y,$N,$S,$W,$E,$ImgX1,$ImgX2,$ImgY1,$ImgY2 if ($Config{"Debug"}); 
1071    my $Width = 256 * (2 ** ($Zoom - $ZOrig));  # Pixel size of tiles 
1072    my $Height = 256; # Pixel height of tile
1073
1074    # svg2png returns true if all tiles extracted were empty. this might break
1075    # if a higher zoom tile would contain data that is not rendered at the
1076    # current zoom level.
1077    my ($success,$empty) = svg2png($Zoom, $ZOrig, $layer, $Width, $Height,$ImgX1,$ImgY1,$ImgX2,$ImgY2,$ImageHeight,$X,$Y,$Ytile);
1078    if (!$success) {
1079       return (0,$empty);
1080    }
1081    if ($empty and !$Config{"Layer.$layer.RenderFullTileset"}) 
1082    {
1083        $SkipEmpty=1;
1084    }
1085
1086    # Get progress percentage
1087    if($SkipEmpty == 1) 
1088    {
1089        # leap forward because this tile and all higher zoom tiles of it are "done" (empty).
1090        for (my $j = $Config{"Layer.$layer.MaxZoom"}; $j >= $Zoom ; $j--) 
1091        {
1092            $progress += 2 ** ($Config{"Layer.$layer.MaxZoom"}-$j);
1093        }
1094    }
1095    else 
1096    {
1097        $progress += 1;
1098    }
1099
1100    if (($progressPercent=$progress*100/(2**($Config{"Layer.$layer.MaxZoom"}-$ZOrig+1)-1)) == 100)
1101    {
1102        statusMessage("Finished $X,$Y for layer $layer", $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent, 1);
1103    }
1104    else
1105    {
1106        if ($Config{Verbose})
1107        {
1108            printf STDERR "Job No. %d %1.1f %% done.\n",$progressJobs, $progressPercent;
1109        }
1110        else
1111        {
1112            statusMessage("Working", $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent,0);
1113        }
1114    }
1115   
1116    # Sub-tiles
1117    my $MercY2 = ProjectF($N); # get mercator coordinates for North border of tile
1118    my $MercY1 = ProjectF($S); # get mercator coordinates for South border of tile
1119    my $MercYC = 0.5 * ($MercY1 + $MercY2); # get center of tile in mercator
1120    my $LatC = ProjectMercToLat($MercYC); # reproject centerline to latlon
1121
1122    my $ImgYCP = ($MercYC - $MercY1) / ($MercY2 - $MercY1); 
1123    my $ImgYC = $ImgY1 + ($ImgY2 - $ImgY1) * $ImgYCP;       # find mercator coordinates for bottom/top of subtiles
1124
1125    my $YA = $Ytile * 2;
1126    my $YB = $YA + 1;
1127
1128    if ($Config{Fork} && $Zoom >= $ZOrig && $Zoom < ($ZOrig + $Config{Fork})) {
1129        my $pid = fork();
1130        if (not defined $pid) 
1131        {
1132            cleanUpAndDie("RenderTile: could not fork, exiting","EXIT",4,$PID); # exit if asked to fork but unable to
1133        }
1134        elsif ($pid == 0) 
1135        {
1136            # we are the child process and can't talk to our parent other than through exit codes
1137            ($success,$empty) = RenderTile($layer, $X, $Y, $YA, $Zoom+1, $ZOrig, $N, $LatC, $W, $E, $ImgX1, $ImgYC, $ImgX2, $ImgY2,$ImageHeight,$SkipEmpty);
1138            if ($success)
1139            {
1140                exit(0);
1141            }
1142            else
1143            {
1144                exit(1);
1145            }
1146        } else {
1147            ($success,$empty) = RenderTile($layer, $X, $Y, $YB, $Zoom+1, $ZOrig, $LatC, $S, $W, $E, $ImgX1, $ImgY1, $ImgX2, $ImgYC,$ImageHeight,$SkipEmpty);
1148            waitpid($pid,0);
1149            my $ChildExitValue = $?; # we don't want the details, only if it exited normally or not.
1150            if ($ChildExitValue or !$success)
1151            {
1152                return (0,$SkipEmpty);
1153            }
1154        }
1155        if ($Zoom == $ZOrig) {
1156            $progressPercent=100 if (! $Config{"Debug"}); # workaround for not correctly updating %age in fork, disable in debug mode
1157            statusMessage("Finished $X,$Y for layer $layer", $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent, 1);
1158        }
1159    } else {
1160        ($success,$empty) = RenderTile($layer, $X, $Y, $YA, $Zoom+1, $ZOrig, $N, $LatC, $W, $E, $ImgX1, $ImgYC, $ImgX2, $ImgY2,$ImageHeight,$SkipEmpty);
1161        return (0,$empty) if (!$success);
1162        ($success,$empty) = RenderTile($layer, $X, $Y, $YB, $Zoom+1, $ZOrig, $LatC, $S, $W, $E, $ImgX1, $ImgY1, $ImgX2, $ImgYC,$ImageHeight,$SkipEmpty);
1163        return (0,$empty) if (!$success);
1164    }
1165
1166    return (1,$SkipEmpty); ## main call wants to know wether the entire tileset was empty so we return 1 for success and 1 if the tile was empty
1167}
1168
1169
1170#-----------------------------------------------------------------------------
1171# Gets latest copy of osmarender from repository
1172#-----------------------------------------------------------------------------
1173sub UpdateClient # FIXME: should be called. (triggered by server?)
1174{
1175    my $Cmd = sprintf("%s%s %s",
1176        $Config{i18n} ? "LC_ALL=C " : "",
1177        $Config{Subversion},
1178        $Config{SubversionUpdateCmd});
1179
1180    statusMessage("Updating the Client", $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent,0);
1181    runCommand($Cmd,$PID); # FIXME: evaluate output and handle locally changed files that need updating!
1182    ## TODO: Implement and check output from svn status, too.
1183
1184}
1185
1186
1187#-----------------------------------------------------------------------------
1188# Transform an OSM file (using osmarender) into SVG
1189#-----------------------------------------------------------------------------
1190sub xml2svg 
1191{
1192    my($MapFeatures, $SVG, $zoom) = @_;
1193    my $TSVG = "$SVG";
1194    my $NoBezier = $Config{NoBezier} || $zoom <= 11;
1195
1196    if (!$NoBezier) 
1197    {
1198        $TSVG = "$SVG-temp.svg";
1199    }
1200
1201    my $XslFile;
1202
1203    $XslFile = "osmarender/osmarender.xsl";
1204
1205    my $Cmd = sprintf("%s \"%s\" tr --maxdepth %s %s %s > \"%s\"",
1206      $Config{"Niceness"},
1207      $Config{"XmlStarlet"},
1208      $Config{"XmlStarletMaxDepth"},
1209      $XslFile,
1210      "$MapFeatures",
1211      $TSVG);
1212
1213    statusMessage("Transforming zoom level $zoom", $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent,0);
1214    runCommand($Cmd,$PID);
1215
1216    # look at temporary svg wether it really is a svg or just the
1217    # xmlstarlet dump and exit if the latter.
1218    open(SVGTEST, "<", $TSVG) || return;
1219    my $TestLine = <SVGTEST>;
1220    chomp $TestLine;
1221    close SVGTEST;
1222
1223    if (grep(!/</, $TestLine))
1224    {
1225       statusMessage("File $TSVG doesn't look like svg, aborting render.", $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent,1);
1226       return cleanUpAndDie("xml2svg failed",$Mode,3,$PID);
1227    }
1228#-----------------------------------------------------------------------------
1229# Process lines to Bezier curve hinting
1230#-----------------------------------------------------------------------------
1231    if (!$NoBezier) 
1232    {   # do bezier curve hinting
1233        my $Cmd = sprintf("%s perl ./lines2curves.pl %s > %s",
1234          $Config{Niceness},
1235          $TSVG,
1236          $SVG);
1237        statusMessage("Beziercurvehinting zoom level $zoom", $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent,0);
1238        runCommand($Cmd,$PID);
1239#-----------------------------------------------------------------------------
1240# Sanitycheck for Bezier curve hinting, no output = bezier curve hinting failed
1241#-----------------------------------------------------------------------------
1242        my $filesize= -s $SVG;
1243        if (!$filesize) 
1244        {
1245            copy($TSVG,$SVG);
1246            statusMessage("Error on Bezier Curve hinting, rendering without bezier curves", $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent,0);
1247        }
1248        killafile($TSVG) if (!$Config{"Debug"});
1249    }
1250    else
1251    {   # don't do bezier curve hinting
1252        statusMessage("Bezier Curve hinting disabled.", $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent,0);
1253    }
1254    return 1;
1255}
1256
1257
1258#-----------------------------------------------------------------------------
1259# Render a SVG file
1260# $ZOrig - the lowest zoom level of the tileset
1261# $X, $Y - tilemnumbers of the tileset
1262# $Ytile - the actual tilenumber in Y-coordinate of the zoom we are processing
1263#-----------------------------------------------------------------------------
1264sub svg2png
1265{
1266    my($Zoom, $ZOrig, $layer, $SizeX, $SizeY, $X1, $Y1, $X2, $Y2, $ImageHeight, $X, $Y, $Ytile) = @_;
1267   
1268    my $TempFile;
1269    my $stdOut;
1270    my $TempDir = $Config{WorkingDirectory} . $PID . "/"; # avoid upload.pl looking at the wrong PNG (Regression caused by batik support)
1271    if (! -e $TempDir ) 
1272    {
1273        mkdir($TempDir) or cleanUpAndDie("cannot create working directory $TempDir","EXIT",3,$PID);
1274    }
1275    elsif (! -d $TempDir )
1276    {
1277        cleanUpAndDie("could not use $TempDir: is not a directory","EXIT",3,$PID);
1278    }
1279    (undef, $TempFile) = tempfile($PID."_part-XXXXXX", DIR => $TempDir, SUFFIX => ".png", OPEN => 0);
1280    (undef, $stdOut) = tempfile("$PID-XXXXXX", DIR => $Config{WorkingDirectory}, SUFFIX => ".stdout", OPEN => 0);
1281
1282   
1283    my $Cmd = "";
1284   
1285    my $Left = $X1;
1286    my $Top = $ImageHeight - $Y1 - ($Y2 - $Y1);
1287    my $Width = $X2 - $X1;
1288    my $Height = $Y2 - $Y1;
1289   
1290    if ($Config{Batik} == "1") # batik as jar
1291    {
1292        $Cmd = sprintf("%s%s java -Xms256M -Xmx%s -jar %s -w %d -h %d -a %f,%f,%f,%f -m image/png -d \"%s\" \"%s%s\" > %s", 
1293        $Config{i18n} ? "LC_ALL=C " : "",
1294        $Config{Niceness},
1295        $Config{BatikJVMSize},
1296        $Config{BatikPath},
1297        $SizeX,
1298        $SizeY,
1299        $Left,$Top,$Width,$Height,
1300        $TempFile,
1301        $Config{WorkingDirectory},
1302        "output-$parent_pid-z$Zoom.svg",
1303        $stdOut);
1304    }
1305    elsif ($Config{Batik} == "2") # batik as executable (wrapper of some sort, i.e. on gentoo)
1306    {
1307        $Cmd = sprintf("%s%s %s -w %d -h %d -a %f,%f,%f,%f -m image/png -d \"%s\" \"%s%s\" > %s",
1308        $Config{i18n} ? "LC_ALL=C " : "",
1309        $Config{Niceness},
1310        $Config{BatikPath},
1311        $SizeX,
1312        $SizeY,
1313        $Left,$Top,$Width,$Height,
1314        $TempFile,
1315        $Config{WorkingDirectory},
1316        "output-$PID-z$Zoom.svg",
1317        $stdOut);
1318    }
1319    else
1320    {
1321        $Cmd = sprintf("%s%s \"%s\" -z -w %d -h %d --export-area=%f:%f:%f:%f --export-png=\"%s\" \"%s%s\" > %s", 
1322        $Config{i18n} ? "LC_ALL=C " : "",
1323        $Config{Niceness},
1324        $Config{Inkscape},
1325        $SizeX,
1326        $SizeY,
1327        $X1,$Y1,$X2,$Y2,
1328        $TempFile,
1329        $Config{WorkingDirectory},
1330        "output-$parent_pid-z$Zoom.svg",
1331        $stdOut);
1332    }
1333   
1334    # stop rendering the current job when inkscape fails
1335    statusMessage("Rendering", $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent,0);
1336    print STDERR $Cmd if (not $Config{"Debug"});
1337    if (not runCommand($Cmd,$PID) or ! -e $TempFile )
1338    {
1339        statusMessage("$Cmd failed", $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent, 1);
1340        ## TODO: check this actually gets the correct coords
1341        PutRequestBackToServer($X,$Y,$ZOrig,"BadSVG");
1342        addFault("inkscape",1);
1343        cleanUpAndDie("svg2png failed",$Mode,3,$PID);
1344        return (0,0);
1345    }
1346    resetFault("inkscape"); # reset to zero if inkscape succeeds at least once
1347    killafile($stdOut) if (not $Config{"Debug"});
1348   
1349    my $ReturnValue = splitImageX($TempFile, $layer, $ZOrig, $X, $Y, $Zoom, $Ytile); # returns true if tiles were all empty
1350   
1351    killafile($TempFile) if (not $Config{"Debug"});
1352    rmdir ($TempDir);
1353    return (1,$ReturnValue); #return true if empty
1354}
1355
1356
1357sub writeToFile 
1358{
1359    open(my $fp, ">", shift()) || return;
1360    print $fp shift();
1361    close $fp;
1362}
1363
1364#-----------------------------------------------------------------------------
1365# Add bounding-box information to an osm-map-features file
1366#-----------------------------------------------------------------------------
1367sub AddBounds 
1368{
1369    my ($Filename,$W,$S,$E,$N,$Size) = @_;
1370   
1371    # Read the old file
1372    open(my $fpIn, "<", "$Filename");
1373    my $Data = join("",<$fpIn>);
1374    close $fpIn;
1375    die("no such $Filename") if(! -f $Filename);
1376   
1377    # Change some stuff
1378    my $BoundsInfo = sprintf(
1379      "<bounds minlat=\"%f\" minlon=\"%f\" maxlat=\"%f\" maxlon=\"%f\" />",
1380      $S, $W, $N, $E);
1381   
1382    $Data =~ s/(<!--bounds_mkr1-->).*(<!--bounds_mkr2-->)/$1\n<!-- Inserted by tilesGen -->\n$BoundsInfo\n$2/s;
1383   
1384    # Save back to the same location
1385    open(my $fpOut, ">$Filename");
1386    print $fpOut $Data;
1387    close $fpOut;
1388}
1389
1390#-----------------------------------------------------------------------------
1391# Set data source file name in map-features file
1392#-----------------------------------------------------------------------------
1393sub SetDataSource 
1394{
1395    my ($Datafile, $Rulesfile) = @_;
1396
1397    # Read the old file
1398    open(my $fpIn, "<", "$Rulesfile");
1399    my $Data = join("",<$fpIn>);
1400    close $fpIn;
1401    die("no such $Rulesfile") if(! -f $Rulesfile);
1402
1403    $Data =~ s/(  data=\").*(  scale=\")/$1$Datafile\"\n$2/s;
1404
1405    # Save back to the same location
1406    open(my $fpOut, ">$Rulesfile");
1407    print $fpOut $Data;
1408    close $fpOut;
1409}
1410
1411#-----------------------------------------------------------------------------
1412# Get the width and height (in SVG units, must be pixels) of an SVG file
1413#-----------------------------------------------------------------------------
1414sub getSize($)
1415{
1416    my $SVG = shift();
1417    open(my $fpSvg,"<",$SVG);
1418    while(my $Line = <$fpSvg>)
1419    {
1420        if($Line =~ /height=\"(.*)px\" width=\"(.*)px\"/)
1421        {
1422            close $fpSvg;
1423            return(($1,$2,1));
1424        }
1425    }
1426    close $fpSvg;
1427    return((0,0,0));
1428}
1429
1430#-----------------------------------------------------------------------------
1431# Temporary filename to store a tile
1432#-----------------------------------------------------------------------------
1433sub tileFilename 
1434{
1435    my($layer,$X,$Y,$Zoom) = @_;
1436    return(sprintf($Config{LocalSlippymap} ? "%s/%s/%d/%d/%d.png" : "%s/%s_%d_%d_%d.png",
1437        $Config{LocalSlippymap} ? $Config{LocalSlippymap} : $JobDirectory,
1438        $Config{"Layer.$layer.Prefix"},
1439        $Zoom,
1440        $X,
1441        $Y));
1442}
1443
1444## sub mergeOsmFiles moved to tahlib.pm
1445
1446#-----------------------------------------------------------------------------
1447# Split a tileset image into tiles
1448#-----------------------------------------------------------------------------
1449sub splitImageX 
1450{
1451    my ($File, $layer, $ZOrig, $X, $Y, $Z, $Ytile) = @_;
1452 
1453    # Size of tiles
1454    my $Pixels = 256;
1455 
1456    # Number of tiles
1457    my $Size = 2 ** ($Z - $ZOrig);
1458
1459    # Assume the tileset is empty by default
1460    my $allempty=1;
1461 
1462    # Load the tileset image
1463    statusMessage(sprintf("Splitting %s (%d x 1)", $File, $Size), $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent, 0);
1464    my $Image = newFromPng GD::Image($File);
1465    if( not defined $Image )
1466    {
1467        print STDERR "\nERROR: Failed to read in file $File\n";
1468        PutRequestBackToServer($X,$Y,$ZOrig,"MissingFile");
1469        cleanUpAndDie("SplitImageX:MissingFile encountered, exiting","EXIT",4,$PID);
1470    }
1471 
1472    # Use one subimage for everything, and keep copying data into it
1473    my $SubImage = new GD::Image($Pixels,$Pixels);
1474 
1475    # For each subimage
1476    for(my $xi = 0; $xi < $Size; $xi++)
1477    {
1478        # Get a tiles'worth of data from the main image
1479        $SubImage->copy($Image,
1480          0,                   # Dest X offset
1481          0,                   # Dest Y offset
1482          $xi * $Pixels,       # Source X offset
1483          0,                   # Source Y offset # always 0 because we only cut from one row
1484          $Pixels,             # Copy width
1485          $Pixels);            # Copy height
1486
1487        # Decide what the tile should be called
1488        my $Filename = tileFilename($layer, $X * $Size + $xi, $Ytile, $Z);
1489        MagicMkdir($Filename) if ($Config{"LocalSlippymap"});
1490   
1491        # Temporary filename
1492        my $Filename2 = "$Filename.cut";
1493        my $Basename = $Filename;   # used for statusMessage()
1494        $Basename =~ s|.*/||;
1495
1496        # Check for black tile output
1497        if (not ($SubImage->compare($BlackTileImage) & GD_CMP_IMAGE)) 
1498        {
1499            print STDERR "\nERROR: Your inkscape has just produced a totally black tile. This usually indicates a broken Inkscape, please upgrade.\n";
1500            PutRequestBackToServer($X,$Y,$ZOrig,"BlackTile");
1501            cleanUpAndDie("SplitImageX:BlackTile encountered, exiting","EXIT",4,$PID);
1502        }
1503        # Detect empty tile here:
1504        elsif (not($SubImage->compare($EmptyLandImage) & GD_CMP_IMAGE)) # libGD comparison returns true if images are different. (i.e. non-empty Land tile) so return the opposite (false) if the tile doesn''t look like an empty land tile
1505        {
1506            copy("emptyland.png", $Filename);
1507            # Change the tile to a zero-length file if it's as blank as the parent
1508            # We keep the ones at level 15 so the server fallback never has to go more than 3 levels.
1509            if( $Z > 12 and $Z != 15 and not $Config{"LocalSlippymap"} )
1510            {
1511                my $upfile = tileFilename($layer, ($X * $Size + $xi)>>1, $Ytile>>1, $Z-1);
1512                my $upsize = -e $upfile ? -s $upfile : -1;
1513                if( $upsize == 0 or $upsize == -s "emptyland.png" )
1514                { open my $fh, ">$Filename" }
1515            }
1516        }
1517        elsif (not($SubImage->compare($EmptySeaImage) & GD_CMP_IMAGE)) # same for Sea tiles
1518        {
1519            copy("emptysea.png",$Filename);
1520            # Change the tile to a zero-length file if it's as blank as the parent
1521            if( $Z > 12 and $Z != 15 and not $Config{"LocalSlippymap"} )
1522            {
1523                my $upfile = tileFilename($layer, ($X * $Size + $xi)>>1, $Ytile>>1, $Z-1);
1524                my $upsize = -e $upfile ? -s $upfile : -1;
1525                if( $upsize == 0 or $upsize == -s "emptysea.png" )
1526                { open my $fh, ">$Filename" }
1527            }
1528#            $allempty = 0; # TODO: enable this line if/when serverside empty tile methods is implemented. Used to make sure we                                     generate all blank seatiles in a tileset.
1529        }
1530        else
1531        {
1532            # If at least one tile is not empty set $allempty false:
1533            $allempty = 0;
1534   
1535            if ($Config{"Layer.$layer.Transparent"}) 
1536            {
1537                $SubImage->transparent($SubImage->colorAllocate(248,248,248));
1538            }
1539            else 
1540            {
1541                $SubImage->transparent(-1);
1542            }
1543
1544            # convert Tile to paletted file This *will* break stuff if different libGD versions are used
1545            # $SubImage->trueColorToPalette($dither,$numcolors);
1546
1547            # Store the tile
1548            statusMessage(" -> $Basename", $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent,0) if ($Config{Verbose});
1549            WriteImage($SubImage,$Filename2);
1550#-----------------------------------------------------------------------------
1551# Run pngcrush on each split tile, then delete the temporary cut file
1552#-----------------------------------------------------------------------------
1553            my $Redirect = ">/dev/null";
1554                if ($^O eq "MSWin32")
1555                {
1556                    $Redirect = "";
1557                }
1558                my $Cmd = sprintf("%s \"%s\" -q %s %s %s",
1559                  $Config{Niceness},
1560                  $Config{Pngcrush},
1561                  $Filename2,
1562                  $Filename,
1563                  $Redirect);
1564
1565            statusMessage("Pngcrushing $Basename", $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent,0);
1566            if(runCommand($Cmd,$PID))
1567            {
1568                unlink($Filename2);
1569            }
1570            else
1571            {
1572                statusMessage("Pngcrushing $Basename failed", $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent,1);
1573                rename($Filename2, $Filename);
1574            }
1575        }
1576        # Assign the job time to this file
1577        utime $JobTime, $JobTime, $Filename;
1578    }
1579    undef $SubImage;
1580    undef $Image;
1581    # tell the rendering queue wether the tiles are empty or not
1582    return $allempty;
1583}
1584
1585#-----------------------------------------------------------------------------
1586# Write a GD image to disk
1587#-----------------------------------------------------------------------------
1588sub WriteImage 
1589{
1590    my ($Image, $Filename) = @_;
1591   
1592    # Get the image as PNG data
1593    my $png_data = $Image->png;
1594   
1595    # Store it
1596    open (my $fp, ">$Filename") || cleanUpAndDie("WriteImage:could not open file for writing, exiting","EXIT",3,$PID);
1597    binmode $fp;
1598    print $fp $png_data;
1599    close $fp;
1600}
1601
1602# sub MagicMkdir moved to tahlib.pm
1603
1604#-----------------------------------------------------------------------------
1605# A function to re-execute the program. 
1606#
1607# This function attempts to detect whether the perl script has changed
1608# since it was invoked initially, and if so, just runs the new version.
1609# This can be used to update the program while it is running (as it is
1610# sometimes hard to hit Ctrl-C at exactly the right moment!)
1611#-----------------------------------------------------------------------------
1612sub reExecIfRequired
1613{
1614    # until proven to work with other systems, only attempt a re-exec
1615    # on linux.
1616    return unless ($^O eq "linux" || $^O eq "cygwin");
1617
1618    my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,
1619        $ctime,$blksize,$blocks) = stat($0);
1620    my $de = "$size/$mtime/$ctime";
1621    if (!defined($dirent))
1622    {
1623        $dirent = $de; 
1624        return;
1625    }
1626    elsif ($dirent ne $de)
1627    {
1628        statusMessage("tilesGen.pl has changed, re-start new version", $Config{Verbose}, $currentSubTask, $progressJobs, $progressPercent, 1);
1629        exec "perl", $0, "loop", "reexec", 
1630            "progressJobs=$progressJobs", 
1631            "idleSeconds=" . getIdle(1), 
1632            "idleFor=" . getIdle(0), 
1633            "progstart=$progstart" or die;
1634    }
1635}
Note: See TracBrowser for help on using the repository browser.