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

Revision 10712, 26.5 KB checked in by matthiasj, 6 years ago (diff)

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

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.