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

Last change on this file since 5992 was 5990, checked in by hakan, 12 years ago

Flag to keep the data file

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