source: subversion/applications/rendering/tilesAtHome_unstable/lib/Tileset.pm @ 10712

Last change on this file since 10712 was 10712, checked in by matthiasj, 10 years ago

the client can now create tileset files when using "tilesGen.pl" upload after rendering in xy mode. No upload of tileset files, yet.

File size: 26.5 KB
Line 
1package Tileset;
2
3=pod
4
5=head1 Tileset package
6
7=head2 Copyright and Authors
8
9Copyright 2006-2008, Dirk-Lueder Kreie, Sebastian Spaeth,
10Matthias Julius and others
11
12This program is free software; you can redistribute it and/or
13modify it under the terms of the GNU General Public License
14as published by the Free Software Foundation; either version 2
15of the License, or (at your option) any later version.
16
17=head2 Description of functions
18
19=cut
20
21use warnings;
22use strict;
23use File::Temp qw/ tempfile tempdir /;
24use Error qw(:try);
25use lib::TahConf;
26use lib::Server;
27use tahlib;
28use tahproject;
29use File::Copy;
30use File::Path;
31
32#-----------------------------------------------------------------------------
33# creates a new Tileset instance and returns it
34# parameter is a request object with x,y,z, and layer atributes set
35# $self->{WorkingDir} is a temporary directory that is only used by this job and
36# which is deleted when the Tileset instance is not in use anymore.
37#-----------------------------------------------------------------------------
38sub new
39{
40    my $class = shift;
41    my $Config = TahConf->getConfig();
42    my $req = shift;    #Request object
43
44    my $self = {
45        req => $req,
46        Config => $Config,
47        JobTime => undef,     # API fetching time for the job as timestamp
48        bbox => undef,        # bbox of required tileset
49        marg_bbox => undef,   # bbox of required tileset including margins
50        childThread => 0,     # marks whether we are a parent or child thread
51        };
52
53    my $delTmpDir = 1-$Config->get('Debug');
54
55    $self->{JobDir} = tempdir( 
56         sprintf("%d_%d_%d_XXXXX",$self->{req}->ZXY),
57         DIR      => $Config->get('WorkingDirectory'), 
58         CLEANUP  => $delTmpDir,
59         );
60
61    bless $self, $class;
62    return $self;
63}
64
65#-----------------------------------------------------------------------------
66# Tileset destructor. Call cleanup in case we did not clean up properly earlier.
67#-----------------------------------------------------------------------------
68sub DESTROY
69{
70    my $self = shift;
71    if ($self->{childThread}) 
72    {   # For whatever unknown reasons this function gets called for exiting child threads.
73        # It really shouldn't but oh well. So protect us and only cleanup if we are the parent.
74        ;
75    } 
76    else
77    {
78        # only cleanup if we are the parent thread
79        $self->cleanup();
80    }
81}
82
83#-----------------------------------------------------------------------------
84# generate does everything that is needed to end up with a finished tileset
85# that just needs compressing and uploading. It outputs status messages, and
86# hands back the job to the server in case of critical errors.
87#-----------------------------------------------------------------------------
88sub generate
89{
90    my $self = shift;
91    my $req =  $self->{req};
92    my $Config = $self->{Config};
93   
94    ::keepLog($$,"GenerateTileset","start","x=".$req->X.',y='.$req->Y.',z='.$req->Z." for layers ".$req->layers_str);
95   
96    my ($N, $S) = Project($req->Y, $req->Z);
97    my ($W, $E) = ProjectL($req->X, $req->Z);
98    $self->{bbox}= bbox->new($N,$E,$S,$W);
99
100    $::progress = 0;
101    $::progressPercent = 0;
102    $::progressJobs++;
103    $::currentSubTask = "Download";
104   
105    ::statusMessage(sprintf("Tileset (%d,%d,%d) around %.2f,%.2f", $req->ZXY, ($N+$S)/2, ($W+$E)/2),1,0);
106   
107    my $maxCoords = (2 ** $req->Z - 1);
108   
109    if ( ($req->X < 0) or ($req->X > $maxCoords) 
110      or ($req->Y < 0) or ($req->Y > $maxCoords) )
111    {
112        my $reason = "Coordinates out of bounds (0..$maxCoords)";
113        ::statusMessage($reason, 1, 0);
114        throw TilesetError $reason;
115    }
116
117    #------------------------------------------------------
118    # Download data (returns full path to data.osm or 0)
119    #------------------------------------------------------
120
121    my $beforeDownload = time();
122    my $FullDataFile = $self->downloadData();
123    ::statusMessage("Download in ".(time() - $beforeDownload)." sec",1,10); 
124
125    #------------------------------------------------------
126    # Handle all layers, one after the other
127    #------------------------------------------------------
128
129    foreach my $layer($req->layers)
130    {
131        #reset progress for each layer
132        $::progress=0;
133        $::progressPercent=0;
134        $::currentSubTask = $layer;
135       
136        # JobDirectory is the directory where all final .png files are stored.
137        # It is not used for temporary files.
138        my $JobDirectory = File::Spec->join($self->{JobDir},
139                                sprintf("%s_%d_%d_%d.dir",
140                                $Config->get($layer."_Prefix"),
141                                $req->ZXY));
142        mkdir $JobDirectory;
143
144        my $maxzoom = $Config->get($layer."_MaxZoom");
145
146        #------------------------------------------------------
147        # Go through preprocessing steps for the current layer
148        # This puts preprocessed files like data-maplint-closeareas.osm in $self->{JobDir}
149        # and returns the file name of the resulting data file.
150        #------------------------------------------------------
151
152        my $layerDataFile = $self->runPreprocessors($layer);
153
154        #------------------------------------------------------
155        # Preprocessing finished, start rendering to SVG
156        # $layerDataFile is just the filename
157        #------------------------------------------------------
158
159        if ($Config->get("Fork")) 
160        {   # Forking to render zoom levels in parallel
161            $self->forkedRender($layer, $maxzoom, $layerDataFile)
162        }
163        else
164        {   # Non-forking render
165            for (my $zoom = $req->Z ; $zoom <= $maxzoom; $zoom++)
166            {
167                $self->GenerateSVG($layerDataFile, $layer, $zoom)
168            }
169        }
170
171        #------------------------------------------------------
172        # Convert from SVG to PNG.
173        #------------------------------------------------------
174       
175        # Find the size of the SVG file
176        my ($ImgH,$ImgW,$Valid) = ::getSize(File::Spec->join($self->{JobDir},
177                                                       "output-z$maxzoom.svg"));
178
179        # Render it as loads of recursive tiles
180        # temporary debug: measure time it takes to render:
181        my $empty = $self->RenderTile($layer, $req->Y, $req->Z, $N, $S, $W, $E, 0,0 , $ImgW, $ImgH, $ImgH);
182
183        #----------
184        # This directory is now ready for upload.
185        # move it up one folder, so it can be picked up.
186        # Unless we have moved everything to the local slippymap already
187        if (!$Config->get("LocalSlippymap"))
188        {
189            my $dircomp;
190
191            my @dirs = File::Spec->splitdir($JobDirectory);
192            do { $dircomp = pop(@dirs); } until ($dircomp ne '');
193            # we have now split off the last nonempty directory path
194            # remove the next path component and add the dir name back.
195            pop(@dirs);
196            push(@dirs, $dircomp);
197            my $DestDir = File::Spec->catdir(@dirs);
198            rename $JobDirectory, $DestDir;
199            # Finished moving directory one level up.
200        }
201    }
202
203    ::keepLog($$,"GenerateTileset","stop",'x='.$req->X.',y='.$req->Y.',z='.$req->Z." for layers ".$req->layers_str);
204
205    # Cleaning up of tmpdirs etc. are called in the destructor DESTROY
206}
207
208#------------------------------------------------------------------
209
210=pod
211
212=head3 downloadData
213
214Download the area for the tileset (whole or in stripes, as required)
215into $self->{JobDir}
216
217B<parameter>: none
218
219B<returns>: filename
220I<filename>: resulting data osm filename (without path).
221
222=cut
223#-------------------------------------------------------------------
224sub downloadData
225{
226    my $self = shift;
227    my $req = $self->{req};
228    my $Config = $self->{Config};
229
230    $::currentSubTask = "Download";
231   
232    # Adjust requested area to avoid boundary conditions
233    my $N1 = $self->{bbox}->N + ($self->{bbox}->N-$self->{bbox}->S)*$Config->get("BorderNS");
234    my $S1 = $self->{bbox}->S - ($self->{bbox}->N-$self->{bbox}->S)*$Config->get("BorderNS");
235    my $E1 = $self->{bbox}->E + ($self->{bbox}->E-$self->{bbox}->W)*$Config->get("BorderWE");
236    my $W1 = $self->{bbox}->W - ($self->{bbox}->E-$self->{bbox}->W)*$Config->get("BorderWE");
237    $self->{marg_bbox} = bbox->new($N1,$E1,$S1,$W1);
238
239    # TODO: verify the current system cannot handle segments/ways crossing the
240    # 180/-180 deg meridian and implement proper handling of this case, until
241    # then use this workaround:
242
243    if($W1 <= -180) {
244      $W1 = -180; # api apparently can handle -180
245    }
246    if($E1 > 180) {
247      $E1 = 180;
248    }
249
250    my $bbox = sprintf("%f,%f,%f,%f",
251      $W1, $S1, $E1, $N1);
252
253    my $DataFile = File::Spec->join($self->{JobDir}, "data.osm");
254   
255    my @predicates;
256    foreach my $layer ($req->layers()) {
257        my %layer_config = $Config->varlist("^${layer}_", 1);
258        if (not $layer_config{"predicates"}) {
259            @predicates = ();
260            last;
261        }
262        my $predicates = $layer_config{"predicates"};
263        # strip spaces in predicates
264        $predicates =~ s/\s+//g;
265        push(@predicates, split(/,/, $predicates));
266    }
267
268    my @OSMServers = (@predicates) ? split(/,/, $Config->get("XAPIServers")) : split(/,/, $Config->get("APIServers"));
269
270    my $Server = Server->new();
271    my $res;
272    my $filelist;
273    foreach my $OSMServer (@OSMServers) {
274        my @URLS;
275        if (@predicates) {
276            foreach my $predicate (@predicates) {
277                my $URL = $Config->get("XAPI_$OSMServer");
278                $URL =~ s/%p/${predicate}/g;                # substitute %p place holder with predicate
279                $URL =~ s/%v/$Config->get('OSMVersion')/ge; # substitute %v place holder with API version
280                push(@URLS, $URL);
281            }
282        }
283        else {
284            my $URL = $Config->get("API_$OSMServer");
285            $URL =~ s/%v/$Config->get('OSMVersion')/ge; # substitute %v place holder with API version
286            push(@URLS, $URL);
287        }
288
289        $filelist = [];
290        my $i=0;
291        foreach my $URL (@URLS) {
292            ++$i;
293            my $partialFile = File::Spec->join($self->{JobDir}, "data-$i.osm");
294            ::statusMessage("Downloading: Map data for " . $req->layers_str, 0, 3);
295           
296            # download tile data in one piece *if* the tile is not too complex
297            if ($req->complexity() < 20_000_000) {
298                my $currentURL = $URL;
299                $currentURL =~ s/%b/${bbox}/g;
300                print "Downloading: $currentURL\n" if ($Config->get("Debug"));
301                try {
302                    $Server->downloadFile($currentURL, $partialFile, 0);
303                    push(@{$filelist}, $partialFile);
304                    $res = 1;
305                }
306                catch ServerError with { # just do nothing if there was an error during download
307                    my $err = shift();
308                    print "Download failed: " . $err->text() . "\n" if ($Config->get("Debug"));;
309                };
310            }
311
312            if ((! $res) and ($Config->get("FallBackToSlices"))) {
313                ::statusMessage("Trying smaller slices",1,0);
314                my $slice = (($E1 - $W1) / 10); # A slice is one tenth of the width
315                for (my $j = 1; $j <= 10; $j++) {
316                    my $bbox = sprintf("%f,%f,%f,%f", $W1 + ($slice * ($j - 1)), $S1, $W1 + ($slice * $j), $N1);
317                    my $currentURL = $URL;
318                    $currentURL =~ s/%b/${bbox}/g;    # substitute bounding box place holder
319                    $partialFile = File::Spec->join($self->{JobDir}, "data-$i-$j.osm");
320                    for (my $k = 1; $k <= 3; $k++) {  # try each slice 3 times
321                        ::statusMessage("Downloading map data (slice $j of 10)", 0, 3);
322                        print "Downloading: $currentURL\n" if ($Config->get("Debug"));
323                        try {
324                            $Server->downloadFile($URL, $partialFile, 0);
325                            $res = 1;
326                        }
327                        catch ServerError with {
328                            my $err = shift();
329                            print "Download failed: " . $err->text() . "\n" if ($Config->get("Debug"));;
330                            my $message = ($k < 3) ? "Download of slice $j failed, trying again" : "Download of slice $j failed 3 times, giving up";
331                            ::statusMessage($message, 0, 3);
332                        };
333                        last if ($res); # don't try again if download was successful
334                    }
335                    last if (!$res); # don't download remaining slices if one fails
336                    push(@{$filelist}, $partialFile);
337                }
338            }
339            if (!$res) {
340                ::statusMessage("Download of data from $OSMServer failed", 0, 3);
341                last; # don't download other URLs if this one failed
342            } 
343        } # foreach @URLS
344
345        last if ($res); # don't try another server if the download was successful
346    } # foreach @OSMServers
347
348    if ($res) {   # Download of data succeeded
349        ::statusMessage("Download of data complete", 1, 10);
350    }
351    else {
352        my $OSMServers = join(',', @OSMServers);
353        throw TilesetError "Download of data failed from $OSMServers", "nodata ($OSMServers)";
354    }
355
356    ::mergeOsmFiles($DataFile, $filelist);
357
358    # Get the API date time for the data so we can assign it to the generated image (for tracking from when a tile actually is)
359    $self->{JobTime} = [stat $DataFile]->[9];
360   
361    # Check for correct UTF8 (else inkscape will run amok later)
362    # FIXME: This doesn't seem to catch all string errors that inkscape trips over.
363    ::statusMessage("Checking for UTF-8 errors",0,3);
364    if (my $line = ::fileUTF8ErrCheck($DataFile))
365    {
366        ::statusMessage(sprintf("found incorrect UTF-8 chars in line %d. job (%d,%d,%d)",$line, $req->ZXY),1,0);
367        throw TilesetError "UTF8 test failed", "utf8";
368    }
369    ::resetFault("utf8"); #reset to zero if no UTF8 errors found.
370    return ($DataFile ,"");
371}
372
373
374#------------------------------------------------------
375# Go through preprocessing steps for the current layer
376# expects $self->{JobDir}/data.osm as input and produces
377# $self->{JobDir}/dataList-of-preprocessors.osm
378# parameter: (layername)
379# returns:   filename (without path)
380#-------------------------------------------------------------
381sub runPreprocessors
382{
383    my $self = shift;
384    my $layer= shift;
385    my $req = $self->{req};
386    my $Config = $self->{Config};
387
388    my @ppchain = ();
389    my $outputFile;
390
391    # config option may be empty, or a comma separated list of preprocessors
392    foreach my $preprocessor(split /,/, $Config->get($layer."_Preprocessor"))
393    {
394        my $inputFile = File::Spec->join($self->{JobDir},
395                                         sprintf("data%s.osm", join("-", @ppchain)));
396        push(@ppchain, $preprocessor);
397        $outputFile = File::Spec->join($self->{JobDir},
398                                          sprintf("data%s.osm", join("-", @ppchain)));
399
400        if (-f $outputFile)
401        {
402            # no action; files for this preprocessing step seem to have been created
403                # by another layer already!
404        }
405        elsif ($preprocessor eq "maplint")
406        {
407            # Pre-process the data file using maplint
408            my $Cmd = sprintf("%s \"%s\" tr %s %s > \"%s\"",
409                    $Config->get("Niceness"),
410                    $Config->get("XmlStarlet"),
411                    "maplint/lib/run-tests.xsl",
412                    "$inputFile",
413                    "tmp.$$");
414            ::statusMessage("Running maplint",0,3);
415            ::runCommand($Cmd,$$);
416            $Cmd = sprintf("%s \"%s\" tr %s %s > \"%s\"",
417                        $Config->get("Niceness"),
418                        $Config->get("XmlStarlet"),
419                        "maplint/lib/convert-to-tags2.xsl",
420                        "tmp.$$",
421                        "$outputFile");
422            ::statusMessage("Creating tags from maplint",0,3);
423            ::runCommand($Cmd,$$);
424            unlink("tmp.$$");
425        }
426        elsif ($preprocessor eq "close-areas")
427        {
428            my $Cmd = sprintf("%s perl close-areas.pl %d %d %d < %s > %s",
429                        $Config->get("Niceness"),
430                        $req->X,
431                        $req->Y,
432                        $req->Z,
433                        "$inputFile",
434                        "$outputFile");
435            ::statusMessage("Running close-areas",0,3);
436            ::runCommand($Cmd,$$);
437        }
438        elsif ($preprocessor eq "area-center")
439        {
440           if ($Config->get("Osmarender") eq "XSLT" && $Config->get("JavaAvailable"))
441           {
442               if ($Config->get("JavaVersion") >= 1.6)
443               {
444                   # use preprocessor only for XSLT for now. Using different algorithm for area center might provide inconsistent results
445                  # on tile boundaries. But XSLT is currently in minority and use different algorithm than orp anyway, so no difference.
446                  my $Cmd = sprintf("%s java -cp %s com.bretth.osmosis.core.Osmosis -q -p org.tah.areaCenter.AreaCenterPlugin --read-xml %s --area-center --write-xml %s",
447                               $Config->get("Niceness"),
448                               join($Config->get("JavaSeparator"), "java/osmosis/osmosis.jar", "java/area-center.jar"),
449                               $inputFile,
450                               $outputFile);
451                   ::statusMessage("Running area-center",0,3);
452                   if (!::runCommand($Cmd,$$))
453                   {
454                       ::statusMessage("Area-center failed, ignoring",0,3);
455                       copy($inputFile,$outputFile);
456                   }
457               } else 
458               {
459                   ::statusMessage("Java version at least 1.6 is required for area-center preprocessor",0,3);
460                   copy($inputFile,$outputFile);
461               }
462           }
463           else
464           {
465              copy($inputFile,$outputFile);
466           }
467        }
468        elsif ($preprocessor eq "noop")
469        {
470            copy($inputFile,$outputFile);
471        }
472        else
473        {
474            throw TilesetError "Invalid preprocessing step '$preprocessor'", $preprocessor;
475        }
476    }
477
478    # everything went fine. Get final filename and return it.
479    my ($Volume, $path, $OSMfile) = File::Spec->splitpath($outputFile);
480    return $OSMfile;
481}
482
483#-------------------------------------------------------------------
484# renders the tiles, using threads
485# paramter: ($layer, $maxzoom)
486#-------------------------------------------------------------------
487sub forkedRender
488{
489    my $self = shift;
490    my ($layer, $maxzoom, $layerDataFile) = @_;
491    my $req = $self->{req};
492    my $Config = $self->{Config};
493    my $minimum_zoom = $req->Z;
494
495    my $numThreads = 2 * $Config->get("Fork");
496    my @pids;
497
498    for (my $thread = 0; $thread < $numThreads; $thread ++) 
499    {
500        # spawn $numThreads threads
501        my $pid = fork();
502        if (not defined $pid) 
503        {   # exit if asked to fork but unable to
504            throw TilesetError "GenerateTileset: could not fork, exiting", "fatal";
505        }
506        elsif ($pid == 0) 
507        {   # we are the child process
508            $self->{childThread}=1;
509            for (my $zoom = ($minimum_zoom + $thread) ; $zoom <= $maxzoom; $zoom += $numThreads) 
510            {
511                try {
512                    $self->GenerateSVG($layerDataFile, $layer, $zoom)
513                }
514                otherwise {
515                    # an error occurred while rendering.
516                    # Thread exits and returns (255+)0 here
517                    exit(0);
518                }
519            }
520            # Rendering went fine, have thread return (255+)1
521            exit(1);
522        } else
523        {   # we are the parent thread, record child pid
524            push(@pids, $pid);
525        }
526    }
527
528    # now wait that all child render processes exited and check their return value
529    # retvalue >> 8 is the real ret value. wait returns -1 if there are no child processes
530    my $success = 1;
531    foreach my $pid(@pids)
532    {
533        waitpid($pid,0);
534        $success &= ($? >> 8);
535    }
536
537    ::statusMessage("exit forked renderer returning $success",0,6);
538    if (not $success) {
539        throw TilesetError "at least one render thread returned an error", "renderer";
540    }
541}
542
543#-----------------------------------------------------------------------------
544# Generate SVG for one zoom level
545#   $layerDataFile - name of the OSM data file (which is in the JobDir)
546#   $Zoom - which zoom currently is processsed
547#-----------------------------------------------------------------------------
548sub GenerateSVG 
549{
550    my $self = shift;
551    my ($layerDataFile, $layer, $Zoom) = @_;
552    my $Config = TahConf->getConfig();
553 
554    # Render the file (returns 0 on failure)
555    if (! ::xml2svg(
556            File::Spec->join($self->{JobDir}, $layerDataFile),
557            $self->{bbox},
558            $Config->get($layer."_Rules.".$Zoom),
559            File::Spec->join($self->{JobDir}, "output-z$Zoom.svg"),
560            $Zoom))
561    {
562        throw TilesetError "Render failure", "renderer";
563    }
564}
565
566
567
568#-----------------------------------------------------------------------------
569# Render a tile
570#   $Ytile, $Zoom - which tilestripe
571#   $Zoom - the cuurent zoom level that we render
572#   $N, $S, $W, $E - bounds of the tile
573#   $ImgX1,$ImgY1,$ImgX2,$ImgY2 - location of the tile in the SVG file
574#   $ImageHeight - Height of the entire SVG in SVG units
575#   returns: allEmpty
576#-----------------------------------------------------------------------------
577sub RenderTile 
578{
579    my $self = shift;
580    my ($layer, $Ytile, $Zoom, $N, $S, $W, $E, $ImgX1,$ImgY1,$ImgX2,$ImgY2,$ImageHeight) = @_;
581    my $Config = TahConf->getConfig();
582    my $maxzoom = $Config->get($layer."_MaxZoom");
583    my $req = $self->{req};
584    my $forkval = $Config->get("Fork");
585
586    return 1 if($Zoom > $maxzoom);
587   
588    # Render it to PNG
589    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", 
590            $Ytile,$req->X,$req->Y,$N,$S,$W,$E,$ImgX1,$ImgX2,$ImgY1,$ImgY2 if ($Config->get("Debug")); 
591
592    my ($FullBigPNGFileName, $reason) = 
593          ::svg2png($self->{JobDir}, $req, $Ytile, $Zoom,$ImgX1,$ImgY1,$ImgX2,$ImgY2,$ImageHeight);
594
595    if (!$FullBigPNGFileName)
596    {  # svg2png failed
597        throw TilesetError $reason, "renderer";
598    }
599
600    # splitImageX returns true if all tiles extracted were empty.
601    # this might break if a higher zoom tile would contain data that is
602    # not rendered at the current zoom level.
603
604    (my $success,my $empty, $reason) = 
605           ::splitImageX($layer, $req, $Zoom, $Ytile, $FullBigPNGFileName);
606    if (!$success)
607    {  # splitimage failed
608        throw TilesetError $reason, "renderer";
609    }
610
611    # If splitimage is empty Should we skip going further up the zoom level?
612    if ($empty and !$Config->get($layer."_RenderFullTileset") and !$Config->get("CreateTilesetFile")) 
613    {
614        # leap forward because in progresscounting as this tile and
615        # all higher zoom tiles of it are "done" (empty).
616        for (my $j = $maxzoom; $j >= $Zoom ; $j--)
617        {
618            $::progress += 2 ** $maxzoom-$j;
619        }
620        return 1;
621    }
622
623    # increase progress of tiles
624    $::progress += 1;
625    $::progressPercent = int( 100 * $::progress / (2**($maxzoom-$req->Z+1)-1) );
626    # if forking, each thread does only 1/nth of tiles so multiply by numThreads
627    ($::progressPercent *= 2*$forkval) if $forkval;
628
629    if ($::progressPercent == 100)
630    {
631        ::statusMessage("Finished ".$req->X.",".$req->Y." for layer $layer",1,0);
632    }
633    (printf STDERR "Job No. %d %1.1f %% done.\n",$::progressJobs, $::progressPercent)
634                    if ($Config->get("Verbose") >= 10);
635   
636    # Sub-tiles
637    my $MercY2 = ProjectF($N); # get mercator coordinates for North border of tile
638    my $MercY1 = ProjectF($S); # get mercator coordinates for South border of tile
639    my $MercYC = 0.5 * ($MercY1 + $MercY2); # get center of tile in mercator
640    my $LatC = ProjectMercToLat($MercYC); # reproject centerline to latlon
641
642    my $ImgYCP = ($MercYC - $MercY1) / ($MercY2 - $MercY1); 
643    my $ImgYC = $ImgY1 + ($ImgY2 - $ImgY1) * $ImgYCP;       # find mercator coordinates for bottom/top of subtiles
644
645    my $YA = $Ytile * 2;
646    my $YB = $YA + 1;
647
648    # we create Fork*2 inkscape threads
649    if ($forkval && $Zoom < ($req->Z + $forkval))
650    {
651        my $pid = fork();
652        if (not defined $pid) 
653        {
654            throw TilesetError "RenderTile: could not fork, exiting", "fatal"; # exit if asked to fork but unable to
655        }
656        elsif ($pid == 0) 
657        {
658            # we are the child process
659            $self->{childThread}=1;
660            try {
661                my $empty = $self->RenderTile($layer, $YA, $Zoom+1, $N, $LatC, $W, $E, $ImgX1, $ImgYC, $ImgX2, $ImgY2,$ImageHeight);
662            }
663            otherwise {
664                exit 0;
665            }
666            # we can't talk to our parent other than through exit codes.
667            exit 1;
668        }
669        else
670        {
671            $self->RenderTile($layer, $YB, $Zoom+1, $LatC, $S, $W, $E, $ImgX1, $ImgY1, $ImgX2, $ImgYC,$ImageHeight);
672            waitpid($pid,0);
673            my $ChildExitValue = ($? >> 8);
674            if (!$ChildExitValue)
675            {
676                throw TilesetError "Forked inkscape failed", "renderer";
677            }
678        }
679    }
680    else
681    {
682        my $empty = $self->RenderTile($layer, $YA, $Zoom+1, $N, $LatC, $W, $E, $ImgX1, $ImgYC, $ImgX2, $ImgY2,$ImageHeight);
683        $empty = $self->RenderTile($layer, $YB, $Zoom+1, $LatC, $S, $W, $E, $ImgX1, $ImgY1, $ImgX2, $ImgYC,$ImageHeight);
684        return $empty;
685    }
686
687    return 0;
688}
689
690
691#------------------------------------------------------------------
692# remove temporary files etc
693#-------------------------------------------------------------------
694sub cleanup
695{
696    my $self = shift;
697    my $Config = $self->{Config};
698
699    # remove temporary job directory if 'Debug' is not set
700    print STDERR "removing job dir",$self->{JobDir},"\n\n" if $Config->get('Debug');
701    rmtree $self->{JobDir} unless $Config->get('Debug');
702}
703
704#----------------------------------------------------------------------------------------
705# bbox->new(N,E,S,w)
706package bbox;
707sub new
708{
709    my $class = shift;
710    my $self={};
711    ($self->{N},$self->{E},$self->{S},$self->{W}) = @_;
712    bless $self, $class;
713    return $self;
714}
715
716sub N { my $self = shift; return $self->{N};}
717sub E { my $self = shift; return $self->{E};}
718sub S { my $self = shift; return $self->{S};}
719sub W { my $self = shift; return $self->{W};}
720
721
722#----------------------------------------------------------------------------------------
723# error class for Tileset
724
725package TilesetError;
726use base 'Error::Simple';
727
7281;
Note: See TracBrowser for help on using the repository browser.