source: subversion/applications/rendering/tilesAtHome/tools/lowzoom/lowzoom.pl @ 3292

Revision 3292, 12.4 KB checked in by damians, 7 years ago (diff)

added some testing, and using of oceantiles-z12.dat

  • Property svn:executable set to *
Line 
1#!/usr/bin/perl
2use strict;
3use LWP::Simple;
4use Image::Magick;
5#------------------------------------------------------------------------------------
6# LowZoom.pl
7# Generates low-zoom map tiles, by downloading high-zoom map tiles, and merging them
8# together.
9#
10# Part of the OpenStreetMap tiles@home project
11#
12# Copyright 2007, Oliver White.
13# Copying license: GNU general public license, v2 or later
14#-----------------------------------------------------------------------------------
15
16$|=1;
17
18## nicked from tahconfig.pm from main t@h.
19## FIXME: use the actual module instead.
20my %Config;
21open(my $fp,"<lowzoom.conf") || die("Can't open \"lowzoom.conf\" ($!)\n");
22while(my $Line = <$fp>){
23    $Line =~ s/#.*$//; # Comments
24    $Line =~ s/\s*$//; # Trailing whitespace
25    if($Line =~ m{
26        ^
27        \s*
28        ([A-Za-z0-9._-]+) # Keyword: just one single word no spaces
29        \s*            # Optional whitespace
30        =              # Equals
31        \s*            # Optional whitespace
32        (.*)           # Value
33        }x){
34
35# Store config options in a hash array
36        $Config{$1} = $2;
37        print "Found $1 ($2)\n" if(0); # debug option
38    }
39}
40close $fp;
41
42# Option: Where to move tiles, so that they get uploaded by another program
43my $uploadDir = $Config{UploadDir};
44
45die "can't find upload directory \"$uploadDir\"" unless (-d $uploadDir);
46
47# Command-line arguments
48my $X = shift();
49my $Y = shift();
50my $Z = shift();
51my $MaxZ = shift() || 12;
52my $Layer = shift() || "tile";
53my $Options = shift();
54
55# Check the command-line arguments, and display usage information
56my $Usage = "Usage: $0 x y z maxZ [layer] [keep]\n  x,y,z are the tile to generate\n  maxZ is the zoom level to download tiles from\n  Options: \n    * layer - which layer to run lowzoom on (tile (default) or maplint)\n    * 'keep' - don't move tiles to an upload area afterwards\nOther options (URLs, upload staging area) are part of the script - change them in source code\n";
57if(($MaxZ > 12)
58  || ($MaxZ <= $Z)
59  || ($Z < 0) || (!defined($Z))
60  || ($MaxZ > 17)
61  || ($X < 0) || (!defined($X))
62  || ($Y < 0) || (!defined($Y))
63  || ($X >= 2 ** $Z)
64  || ($Y >= 2 ** $Z)
65  ){
66  die($Usage);
67}
68
69# Timestamp to assign to generated tiles
70my $Timestamp = time();
71
72# What we intend to do
73my $Status = new status; 
74$Status->area($Layer,$X,$Y,$Z,$MaxZ);
75
76#open oceantiles.dat and leave it open if UseOceantilesDat is on
77my $oceantiles;
78if ($Config{UseOceantilesDat}) {
79        open($oceantiles, "<", "../../oceantiles_12.dat") or die("../../oceantiles-z12.dat not found");
80}
81
82#open a file for the suspicious tiles
83my $suspiciousTiles;
84if ($Config{WriteSuspicious}){
85        open($suspiciousTiles, ">", "suspicious_tiles.txt");
86}
87
88# Create the requested tile
89lowZoom($X,$Y,$Z, $MaxZ, $Layer, $Status);
90
91# Move all low-zoom tiles to upload directory
92moveTiles(tempdir(), $uploadDir, $MaxZ) if($Options ne "keep");
93
94# Status message, saying what we did
95$Status->final();
96
97# Recursively create (including any downloads necessary) a tile
98sub lowZoom {
99  my ($X,$Y,$Z,$MaxZ, $Prefix, $Status) = @_;
100 
101  # Get tiles
102  if($Z >= $MaxZ){
103                downloadtile($X,$Y,$Z,$Layer);
104  }
105  else{
106    # Recursively get/create the 4 subtiles
107    lowZoom($X*2,$Y*2,$Z+1,$MaxZ, $Status);
108    lowZoom($X*2+1,$Y*2,$Z+1,$MaxZ, $Status);
109    lowZoom($X*2,$Y*2+1,$Z+1,$MaxZ, $Status);
110    lowZoom($X*2+1,$Y*2+1,$Z+1,$MaxZ, $Status);
111 
112    # Create the tile from those subtiles
113    supertile($X,$Y,$Z,$Layer);
114  }
115}
116# Download a tile from the tileserver
117sub downloadtile {
118  my ($X,$Y,$Z,$Layer) = @_;
119  my $f1 = remotefile($X,$Y,$Z,$Layer);
120  my $f2 = localfile($X,$Y,$Z,$Layer);
121 
122  mirror($f1,$f2);
123 
124  my $Size = -s $f2;
125  $Status->downloadCount($Layer,$X,$Y,$Z,$Size);
126 
127        #check the tile
128        if ($Config{UseOceantilesDat}) {
129                if ($Z eq 12) {
130                        if (($Size == 103) && (askOceantiles($X,$Y) eq "land")) {
131                                if ($Config{WriteSuspicious}) {
132                                        print $suspiciousTiles "$X $Y : downloaded tile is sea, oceantiles says land \n";
133                                }
134                                if ($Config{BlankSource} eq "oceantiles") {
135                                        unlink $f2;
136                                        link("../../emptyland.png", $f2);
137                                }
138                        }elsif (($Size == 179) && (askOceantiles($X,$Y) eq "sea")) {
139                                if ($Config{WriteSuspicious}) {
140                                        print $suspiciousTiles "$X $Y : downloaded tile is land, oceantiles says sea \n";
141                                }
142                                if ($Config{BlankSource} eq "oceantiles") {
143                                        unlink $f2;
144                                        link("../../emptysea.png", $f2);
145                                }
146                        }
147                }
148        }
149
150  # Don't bother storing blank or invalid tiles
151        unlink $f2 if($Size < 103);
152
153}
154
155# Delete blank subtiles of a blank tile
156# When we notice that a tile is blank because all its subtiles are blank,
157# because of the fallback mechanism in the server we can delete those
158# subtiles. However, to avoid the fallback on the server having to work too
159# hard, we ensure that there are still real blank tiles every few zoom
160# levels.
161sub deleteBlankSubtiles
162{
163  my($X,$Y,$Z,$Layer) = @_;
164  return if $Z >= 11;  # Not lowzoom's problem
165  # This keeps real blank tiles at zooms 3,6 and 9
166  return if ($Z+1)%3 == 0;
167 
168  for my $x (0,1)
169  {
170    for my $y (0,1)
171    {
172      my $f = localfile(2*$X + $x, 2*$Y + $y, $Z+1, $Layer);
173      # Unlink prior to creating, file may be a hard link
174      unlink $f;
175      open my $fh, ">", $f;  # Make zero byte file, the marker for the server to delete the tile
176    }
177  }
178}
179# Create a supertile, by merging together 4 local image files, and creating a new local file
180sub supertile {
181  my ($X,$Y,$Z,$Layer) = @_;
182 
183  # Load the subimages
184  my $AA = readLocalImage($X*2,$Y*2,$Z+1,$Layer);
185  my $BA = readLocalImage($X*2+1,$Y*2,$Z+1,$Layer);
186  my $AB = readLocalImage($X*2,$Y*2+1,$Z+1,$Layer);
187  my $BB = readLocalImage($X*2+1,$Y*2+1,$Z+1,$Layer);
188 
189        my $Filename = localfile($X,$Y,$Z,$Layer);
190        # Always delete file first. The use of hardlinks means we might accedently overwrite other files.
191        unlink($Filename);
192        print "generating $Filename \n";
193
194        # all images the same size?
195        if ($AA == undef) { $AA = Image::Magick->new; }
196        if ($AB == undef) { $AB = Image::Magick->new; }
197        if ($BA == undef) { $BA = Image::Magick->new; }
198        if ($BB == undef) { $BB = Image::Magick->new; }
199
200        if(($AA->Get('filesize') == 103 )  && ($AA->Get('filesize') == $BA->Get('filesize')) && ($BA->Get('filesize') == $AB->Get('filesize')) && ( $AB->Get('filesize') == $BB->Get('filesize')) ) 
201        {#if its a "404 sea" or a "sea.png" and all 4 sizes are the same, make one 69 bytes sea of it
202                        my $SeaFilename = "../../emptysea.png"; 
203                        link($SeaFilename,$Filename);
204                        deleteBlankSubtiles($X,$Y,$Z,$Layer);
205                        return;
206        }
207        elsif(($AA->Get('filesize') == 179 ) && ($AA->Get('filesize') == $BA->Get('filesize')) && ($BA->Get('filesize') == $AB->Get('filesize')) && ( $AB->Get('filesize') == $BB->Get('filesize')) ) 
208        {#if its a "blank land" or a "land.png" and all 4 sizes are the same, make one 69 bytes land of it
209                        my $LandFilename = "../../emptyland.png"; 
210                        link($LandFilename,$Filename);
211                        deleteBlankSubtiles($X,$Y,$Z,$Layer);
212                        return;
213        }
214        else{
215                my $Image = Image::Magick->new;
216
217                # Create the supertile
218                $Image->Set(size=>'512x512');
219                $Image->ReadImage('xc:white');
220
221                # Copy the subimages into the 4 quadrants
222                foreach my $x (0, 1)
223                {
224                                foreach my $y (0, 1)
225                                {
226                                                next unless (($Z < 9) || (($x == 0) && ($y == 0)));
227                                                $Image->Composite(image => $AA, 
228                                                                                geometry => sprintf("512x512+%d+%d", $x, $y),
229                                                                                compose => "darken") if ($AA);
230
231                                                $Image->Composite(image => $BA, 
232                                                                                geometry => sprintf("512x512+%d+%d", $x + 256, $y),
233                                                                                compose => "darken") if ($BA);
234
235                                                $Image->Composite(image => $AB, 
236                                                                                geometry => sprintf("512x512+%d+%d", $x, $y + 256),
237                                                                                compose => "darken") if ($AB);
238
239                                                $Image->Composite(image => $BB, 
240                                                                                geometry => sprintf("512x512+%d+%d", $x + 256, $y + 256),
241                                                                                compose => "darken") if ($BB);
242                                }
243                }
244
245                $Image->Scale(width => "256", height => "256");
246                $Image->Set(type=>"Palette");
247                $Image->Set(quality => 90);  # compress image
248                $Image->Write($Filename);
249                utime $Timestamp, $Timestamp, $Filename;
250        }
251
252         #remove tiles which will not be uploaded
253        if (($Z == $MaxZ ) && ($Options ne "keep")){
254                for my $x (0,1)
255                {
256                        for my $y (0,1)
257                        {
258                                my $f = localfile(2*$X + $x, 2*$Y + $y, $Z+1, $Layer);
259                                unlink $f;
260                        }
261                }
262        };
263
264}
265
266# Open a PNG file, and return it as a Magick image (or 0 if not found)
267sub readLocalImage
268{
269    my ($X,$Y,$Z,$Layer) = @_;
270    my $Filename = localfile($X,$Y,$Z,$Layer); 
271    if (!-f $Filename)
272    {
273        return undef;
274    }
275    my $Image = new Image::Magick;
276    if (my $err = $Image->Read($Filename))
277    {
278        print STDERR "$err\n";
279        return undef;
280    }
281                if ($Image->Get('filesize') == 69) 
282                {
283                        # do not return 1x1 pixel images since we might have to put them into a lower zoom
284                        @$Image=();
285                        if (my $err = $Image->Read("sea.png"))
286                        {
287                                        print STDERR "$err\n";
288                                        return undef;
289                        }
290                }
291                if ($Image->Get('filesize') == 67) 
292                {
293                        # do not return 1x1 pixel images since we might have to put them into a lower zoom
294                        @$Image=();
295                        if (my $err = $Image->Read("land.png"))
296                        {
297                                        print "$err\n";
298                                        return undef;
299                        }
300                }
301    return($Image);
302}
303
304# Take any tiles that were created (as opposed to downloaded), and move them to
305# an area ready for upload.
306# + Delete any tiles that were downloaded
307sub moveTiles {
308  my ($from, $to, $MaxZ) = @_;
309  opendir(my $dp, $from) || die($!);
310  while(my $file = readdir($dp)){
311    if($file =~ /^${Layer}_(\d+)_(\d+)_(\d+)\.png$/o){
312      my ($Z,$X,$Y) = ($1,$2,$3);
313      my $f1 = "$from/$file";
314      my $f2 = "$to/$file";
315      if($Z < $MaxZ){
316        # Rename can fail if the target is on a different filesystem
317        rename($f1, $f2) or system("mv",$f1,$f2);
318      }
319      else{
320        unlink $f1;
321      }
322    }
323  } 
324  close $dp;
325}
326
327# takes x and y coordinates and returns if the corresponding tile
328# should be sea or land
329sub askOceantiles {
330        my ($X, $Y) = @_;
331
332        my $tileoffset = ($Y * (2**12)) + $X;
333        seek $oceantiles, int($tileoffset / 4), 0; 
334        my $buffer;
335        read $oceantiles, $buffer, 1;
336        $buffer = substr( $buffer."\0", 0, 1 );
337        $buffer = unpack "B*", $buffer;
338        my $str = substr( $buffer, 2*($tileoffset % 4), 2 );
339
340#       print("lookup handler finds: $str\n") ;
341        if ($str eq "10") {     return "sea"; };
342        if ($str eq "01") {      return "land"; };
343        if ($str eq "11") {     return "land"; };
344
345        return "unknown";
346
347        # $str eq "00" => unknown (not yet checked)
348        # $str eq "01" => known land
349        # $str eq "10" => known sea
350        # $str eq "11" => known edge tile
351
352}
353
354# Option: filename for our temporary map tiles
355# (note: this should match whatever is expected by the upload scripts)
356sub localfile {
357  my ($X,$Y,$Z,$Layer) = @_;
358  return sprintf("%s/%s_%d_%d_%d.png", tempdir(), $Layer,$Z,$X,$Y);
359}
360# Option: URL for downloading tiles
361sub remotefile {
362  my ($X,$Y,$Z,$Layer) = @_;
363  return sprintf("http://dev.openstreetmap.org/~ojw/Tiles/%s.php/%d/%d/%d.png", $Layer,$Z,$X,$Y);
364}
365# Option: what to use as temporary storage for tiles
366sub tempdir {
367  return("temp");
368}
369
370package status;
371use Time::HiRes qw(time); # Comment-this out if you want, it's not important
372sub new {
373  my $self  = {};
374  $self->{DONE} = 0;
375  $self->{TODO} = 1;
376  $self->{SIZE} = 0;
377  bless($self);
378  return $self;
379}
380sub downloadCount(){
381  my $self = shift();
382  $self->{LAYER} = shift();
383  $self->{LAST_X} = shift();
384  $self->{LAST_Y} = shift();
385  $self->{LAST_Z} = shift();
386  $self->{LAST_SIZE} = shift();
387  $self->{DONE}++;
388  $self->{SIZE} += $self->{LAST_SIZE};
389  $self->{PERCENT} = $self->{TODO} ? (100 * ($self->{DONE} / $self->{TODO})) : 0;
390  $self->display();
391}
392sub area(){
393  my $self = shift();
394  $self->{LAYER}=shift();
395  $self->{X} = shift();
396  $self->{Y} = shift();
397  $self->{Z} = shift();
398  $self->{MAX_Z} = shift();
399  $self->{RANGE_Z} = $self->{MAX_Z} - $self->{Z};
400  $self->{TODO} = 4 ** $self->{RANGE_Z};
401  $self->display();
402  $self->{START_T} = time();
403}
404sub update(){
405  my $self = shift();
406  $self->{T} = time();
407  $self->{DT} = $self->{T} - $self->{START_T};
408  $self->{EXPECT_T} = $self->{DONE} ? ($self->{TODO} * $self->{DT} / $self->{DONE}) : 0;
409  $self->{EXPECT_FINISH} = $self->{START_T} + $self->{EXPECT_T};
410  $self->{REMAIN_T} = $self->{EXPECT_T} - $self->{EXPECT_DT};
411}
412sub display(){
413  my $self = shift();
414  $self->update();
415 
416  printf( "Job %s(%d,%d,%d): %03.1f%% done, %1.1f min (%d,%d,%d = %1.1f KB)\r", 
417    $self->{LAYER},
418    $self->{X},
419    $self->{Y},
420    $self->{Z},
421    $self->{PERCENT}, 
422    $self->{REMAIN_T} / 60,
423    $self->{LAST_X},
424    $self->{LAST_Y},
425    $self->{LAST_Z},
426    $self->{LAST_SIZE}/1024
427    );
428}
429sub final(){
430  my $self = shift();
431  $self->{END_T} = time();
432  printf("Done, %d downloads, %1.1fKB total, took %1.0f seconds\n",
433    $self->{DONE},
434    $self->{SIZE} / 1024,
435    $self->{DT});
436}
437
438
Note: See TracBrowser for help on using the repository browser.