source: subversion/utils/pdf-atlas/pdf-atlas.pl @ 2013

Last change on this file since 2013 was 2013, checked in by joerg, 13 years ago

rename pdf-atlas binary

  • Property svn:executable set to *
File size: 35.3 KB
Line 
1#!/usr/bin/perl
2
3BEGIN {
4    my $dir = $0;
5    $dir =~s,[^/]+/[^/]+$,,;
6    unshift(@INC,"$dir/perl");
7
8    unshift(@INC,"../perl");
9    unshift(@INC,"~/svn.openstreetmap.org/utils/perl");
10    unshift(@INC,"$ENV{HOME}/svn.openstreetmap.org/utils/perl");
11}
12
13use strict;
14use warnings;
15
16use PDF::API2;
17use Data::Dumper;
18use constant mm => 25.4/72;
19use Carp qw(cluck confess);
20use Getopt::Long;
21use Pod::Usage;
22use File::Basename;
23
24use Geo::GPX::File;
25use Geo::Geometry;
26use Geo::OSM::Planet;
27use Geo::OSM::SegmentList;
28use Geo::OSM::Tracks2OSM;
29use Geo::OSM::Write;
30use Geo::Filter::Area;
31use Geo::Tracks::GpsBabel;
32use Geo::Tracks::Kismet;
33use Geo::Tracks::NMEA;
34use Geo::Tracks::Tools;
35use Utils::Debug;
36use Utils::File;
37use Utils::Math;
38use Geo::OSM::MapFeatures;
39
40sub FillDefaults($); # {}
41sub CreateAtlas($); #{}
42sub LoadOSM($$); #{}
43sub AddTitlePage($$); #{}
44sub TextPage($$); #{}
45sub AddMetaInfo($$); #{}
46sub LoadData($); #{}
47sub ReadFile($); #{}
48sub BBoxPages($); #{}
49sub ContentsPage($$); #{}
50sub mapGrid($$$$$$$); #{}
51sub PageFrame($$); #{}
52sub ConvertPDF($); #{}
53
54my $ConfigFile = "Config/config.txt";
55Getopt::Long::Configure('no_ignore_case');
56print STDERR "Options: -".join(",",@ARGV)."-\n" if $DEBUG;
57my $CommandLineOptions;
58my $ResultDir=osm_dir().'/pdf-atlas';
59our $man=0;
60our $help=0;
61our $force_update=0;
62our $no_png =0;
63our $no_html =0;
64
65GetOptions ( 
66             'debug+'     => \$DEBUG,
67             'd+'         => \$DEBUG,
68             'v+'         => \$VERBOSE,
69             'verbose+'   => \$VERBOSE,
70             'MAN'        => \$man, 
71             'man'        => \$man, 
72             'h|help|x'   => \$help, 
73
74             'no-png'     => \$no_png,
75             'no-html'     => \$no_html,
76             'forceupdate'   => \$force_update,
77             'force-update'   => \$force_update,
78             'c:s'        => \$ConfigFile,
79             'config:s'   => \$ConfigFile,
80             'Places:s'   => \$CommandLineOptions->{Places},
81             'ResultDir:s'=> \$ResultDir,
82             'Data:s'     => \$CommandLineOptions->{Data},
83             'csv:s'      => \$CommandLineOptions->{Data},
84             );
85
86pod2usage(1) if $help;
87pod2usage(-verbose=>2) if $man;
88
89mkdir_if_needed( $ResultDir );
90
91print "Rest Options: -".join(",",@ARGV)."-\n";
92 
93# Read the configuration file
94my $Options = ReadFile($ConfigFile);
95printf STDERR "CreateAtlas() Options: ".Dumper(\$Options)."\n" 
96    if $DEBUG>12;
97
98for my $k ( keys %{$CommandLineOptions} ) {
99    my $value = $CommandLineOptions->{$k};
100    $Options->{$k}=$value if defined $value;
101    printf STDERR "CreateAtlas() Additional Option %-12s: $Options->{$k}\n",$k 
102        if $DEBUG>2;     
103}
104
105if ( shift() ) {
106    die("Usage: $0 [-d] [--config=<Config file>]\n");
107};
108
109printf STDERR "Memory usage: %s\n",mem_usage() if $DEBUG>1;
110
111FillDefaults($Options);
112
113my $data_file = expand_filename($Options->{"Data"});
114if ( ! -s ($data_file)){
115    die "!!!!!!!!!!! ERROR: Data File $data_file is missing\n";
116}
117
118my $pdf_filename=$ResultDir."/".$Options->{"Filename"};
119if ( $force_update ||
120     file_needs_re_generation($data_file, $pdf_filename) ||
121     file_needs_re_generation($ConfigFile, $pdf_filename) ){
122    CreateAtlas($Options);
123} else {
124    print "$pdf_filename is Up to Date\n";
125}
126
127ConvertPDF($pdf_filename);
128
129exit;
130
131
132#---------------------------------------------------------
133# Convert a pdf into a list of thumbnails
134# and build a html page arround it
135sub ConvertPDF($){
136    my $FN_in = shift;
137    my $convert=`which convert`;
138    chomp $convert;
139    unless ( $convert !~ m/(^convert|not found)/){
140        print "convert not found, so not converting to png nor building html Page\n";
141        return;
142    }
143
144    my $basename=basename($FN_in);
145    $basename =~ s/\.pdf$//;
146    my $base_dir = dirname($FN_in);
147
148    if ( ! -s $FN_in ) {
149        print STDERR "No pdf file '$FN_in' found, so no conversion.\n";
150        return;
151    }
152
153    return if $no_png;
154
155    my $FN_out_0 = dirname($FN_in)."/thumbs/TN_$basename-0.png";
156    # convert file.pdf --> thumbs/TN_file_*.png
157    if ( $force_update  || 
158         file_needs_re_generation($pdf_filename,$FN_out_0) ){
159        print STDERR "$pdf_filename will be converted to  $FN_out_0\n";
160        mkdir_if_needed( dirname($FN_in).'/thumbs');
161        my $FN_out = dirname($FN_in)."/thumbs/TN_$basename.png";
162        # Convert osm_atlas-xy.pdf --> thumbs/TN_osm_atlas-xy-[0..n].png
163        print STDERR "Convert PDF: $convert '$FN_in' '$FN_out' \n"
164            if $DEBUG;
165        my $result =`$convert '$FN_in' '$FN_out'`;
166    } else {
167        print "$FN_out_0 is Up to Date\n";
168    }
169
170    # Build Html Page for this pdf File
171    return if $no_html;
172    my $FN_pdf_html = dirname($FN_in)."/index_$basename.html";
173    if ( $force_update  || 
174         file_needs_re_generation($pdf_filename,$FN_pdf_html)||
175         file_needs_re_generation($FN_out_0,$FN_pdf_html) ){
176        print STDERR "$pdf_filename will be converted to  $FN_pdf_html\n";
177        my $fh_html = IO::File->new(">$FN_pdf_html");
178        print $fh_html "<html>\n";
179        print $fh_html "<title>Index for $basename</title>\n";
180        print $fh_html "<body>\n";
181        print $fh_html "<h1>Index for $basename</h1>\n";
182       
183        print  $fh_html "     <a href=\"$basename.pdf\">\n";
184        print  $fh_html "       Link to Complete PDF File $basename.pdf\n";
185        print  $fh_html "     </a>\n";
186        print  $fh_html "     <br>\n";
187        print  $fh_html "     <br>\n";
188
189        print $fh_html "<table CELLSPACING=\"0\" BORDER=\"1\"><tr>";
190        my $index=0;
191        while ( -s "$base_dir/thumbs/TN_$basename-$index.png" ) {
192            my $img = "thumbs/TN_$basename-$index.png";
193            print  $fh_html "  <td>\n";
194            print  $fh_html "     <a href=\"$img\">\n";
195            print  $fh_html "       <img src=\"$img\">\n";
196            print  $fh_html "     </a>\n";
197            print  $fh_html "  </td>\n";
198            print  $fh_html "</tr>\n<tr>\n" if $index % 2;
199            $index++;
200        }
201        print $fh_html "</table\n";
202        print $fh_html "</html>\n";
203        print $fh_html "</body>\n";
204        $fh_html->close();
205    } else {
206        print "$FN_pdf_html is Up to Date\n";
207    }
208
209    # Build general Index Page
210    my $FN_index_html = dirname($FN_in)."/index.html";
211    if ( $force_update  || 
212         file_needs_re_generation($FN_pdf_html,$FN_index_html) ){
213        # Build Overview Html Page
214        my $FN_dir = dirname($FN_in);
215        my $fh_html = IO::File->new(">$FN_index_html");
216        print $fh_html "<html>\n";
217        print $fh_html "<title>Index of PDF Files</title>\n";
218        print $fh_html "<body>\n";
219        print $fh_html "<h1>Index of PDF Files</h1>\n";
220        print $fh_html "<table CELLSPACING=\"0\" BORDER=\"1\"><tr>";
221        my $index=0;
222        for my $filename ( sort glob("$FN_dir/*.pdf") ) {
223            my $file=basename($filename);
224            $file=~ s/.pdf//;
225            my $name = $file;
226            $name =~ s/osm_atlas-//;
227            my $img = "thumbs/TN_$file-1.png";
228            print  $fh_html "  <td> $name <br>\n";
229            print  $fh_html "    <a href=\"index_$file.html\">\n";
230            print  $fh_html "       <img height=\"600\" width=\"450\" src=\"$img\">\n";
231            print  $fh_html "    </a>\n";
232            print  $fh_html "  </td>\n";
233            print  $fh_html "</tr>\n<tr>\n" if $index % 2;
234            $index++;
235        }
236        print $fh_html "</table\n";
237        print $fh_html "</html>\n";
238        print $fh_html "</body>\n";
239        $fh_html->close();
240    } else {
241        print "$FN_index_html is Up to Date\n";
242    }
243}
244
245#---------------------------------------------------------
246sub FillDefaults($){
247    # Setting defaults
248    my $Options = shift;
249    my ($guess_name) = ( $Options->{Places}  =~ m/places-(.+).txt/ );
250    unless (defined( $Options->{"Filename"} )) {
251        die "Cannot guess Filename" unless $guess_name;
252        $Options->{"Filename"} = "osm_atlas-$guess_name.pdf";
253        }
254    unless (defined( $Options->{"Title"} )) {
255        die "Cannot guess name for defaults" unless $guess_name;
256        $Options->{"Title"} = "OSM $guess_name Atlas";
257    }
258}
259
260#---------------------------------------------------------
261# Create a PDF road atlas, based on options in a config file
262#---------------------------------------------------------
263sub CreateAtlas($){
264  my $Options = shift;
265  my $PDF = PDF::API2->new();
266
267  # Title page
268  AddTitlePage($PDF, $Options->{"Title"}) 
269      if $Options->{"Title"};
270 
271  BBoxPages( $Options);
272  my $Data = LoadData($Options);
273#  $Data->{map_features}->load_icons($PDF);
274  $PDF = MapPages($PDF, $Options, $Data);
275 
276  # License page
277  TextPage($PDF, $Options->{"License"});
278 
279  # Save the PDF
280  my $pdf_filename=$ResultDir."/".$Options->{"Filename"};
281  printf STDERR "Saving %s\n", $pdf_filename;
282  $PDF->saveas($pdf_filename);
283}
284
285#---------------------------------------------------------
286# Adds a title page to the PDF document
287#---------------------------------------------------------
288sub AddTitlePage($$)
289{
290  my ($PDF, $Title) = @_;
291  my $Font = $PDF->corefont('Helvetica');
292 
293  # Add file meta-informationshift()
294  AddMetaInfo($PDF, $Title);
295   
296  # Create a new page for the title
297  my $Page = $PDF->page;
298 
299  my $TextHandler = $Page->text;
300  $Page->mediabox(210/mm, 297/mm);
301  $Page->cropbox (10/mm, 10/mm, 200/mm, 287/mm);
302 
303  # Write the page title (TODO: read this from a text file)
304  foreach my $Line(
305    ("105, 200, 11, centre, black, $Title",
306    "105, 60, 6, centre, black, Created from OpenStreetMap data",
307    "105, 50, 6, centre, black, http://openstreetmap.org.uk/",
308    "105, 40, 6, centre, black, Published under a Creative Commons license",
309    "105, 20, 6, centre, black, Creation Date: ".localtime(time()),
310    ))
311    {
312    my ($X, $Y, $Size, $Pos, $Colour, $Text) = split(/,\s+/, $Line);
313   
314    $TextHandler->font($Font, $Size/mm );
315    $TextHandler->fillcolor($Colour);
316    $TextHandler->translate($X/mm, $Y/mm);
317   
318    $TextHandler->text($Text) if($Pos eq "left");
319    $TextHandler->text_center($Text) if($Pos eq "centre");
320    $TextHandler->text_right($Text) if($Pos eq "right");
321    }
322}
323
324#---------------------------------------------------------
325# Adds a page of preformatted text, from a text file
326#---------------------------------------------------------
327sub TextPage($$)
328{
329  my ($PDF, $Filename) = @_;
330  my $Page = $PDF->page;
331  $Filename =~ s/\~/$ENV{HOME}/;
332
333  my $TextHandler = $Page->text;
334  $TextHandler->font($PDF->corefont('Helvetica'), 6/mm );
335  $TextHandler->fillcolor('black');
336  printf STDERR "Reading TextPage $Filename\n";
337  open(my $fp, "<", $Filename) 
338      || die("Can't open $Filename ($!)\n");
339 
340  my ($x, $y) = (30, 250);
341  foreach my $Line(<$fp>){
342    chomp $Line;
343    $TextHandler->translate($x/mm, $y/mm);
344    $TextHandler->text($Line);
345    $y -= 7;
346  }
347}
348#---------------------------------------------------------
349# Adds meta-information to a PDF
350#---------------------------------------------------------
351sub AddMetaInfo($$)
352{
353  my ($PDF, $Title) = @_;
354 
355  # Timestamp (using perl standard functions to make script easier to install)
356  my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime(time);
357  my $Timestamp = sprintf("D:%04d%02d%02d%02d%02d%02d+00;00", $year+1900, $mon+1, $mday, $hour, $min, $sec);
358 
359  $PDF->info(
360        'Author'       => "OpenStreetMap community",
361        'CreationDate' => $Timestamp,
362        'ModDate'      => $Timestamp,
363        'Creator'      => "OJW's script",
364        'Producer'     => "PDF::API2",
365        'Title'        => $Title,
366        'Subject'      => "Cartography",
367        'Keywords'     => "OpenStreetMap"
368    );
369}
370
371sub LoadData($)
372{
373  my ($Options) = @_;
374  my %Data;
375 
376  foreach my $Datatype("Styles","Equivalences","Filters")
377  {
378    my $Filename = $Options->{$Datatype};
379    $Data{$Datatype} = ReadFile($Filename);
380  }
381 
382  die "No Area defined for ".Dumper($Options)."\n" 
383      unless defined $Options->{"Area"};
384  print "Area: '$Options->{Area}'\n" if $DEBUG;
385  my ($LatC, $LongC, $Size) = split(/\s*,\s*/,$Options->{"Area"});
386  my $Margin = 1.5;
387  my %Bounds = (
388    "W" => $LongC - $Margin * $Size,
389    "E" => $LongC + $Margin * $Size,
390    "S" => $LatC - $Margin * $Size,
391    "N" => $LatC + $Margin * $Size);
392
393  print "Bounds: ".Dumper(\%Bounds) if $DEBUG>10;
394  die "No Size defined for ".Dumper($Options)."\n" unless $Size;
395
396  $Data{map_features} = Geo::OSM::MapFeatures->load();
397
398  $Data{"Coast"} = LoadGSHHS($Options->{"Coast"}, \%Bounds) if($Options->{"Coast"});
399  printf STDERR "Memory usage: %s\n",mem_usage() if $DEBUG>1;
400
401  $Data{"Segments"} = LoadOSM($Options->{"Data"}, \%Bounds) if($Options->{"Data"});
402  printf STDERR "Memory usage: %s\n",mem_usage() if $DEBUG>1;
403
404#  $Data{"Points"} = LoadOSM($Options->{"Data_POI"}, \%Bounds) if($Options->{"Data_POI"});
405  printf STDERR "Memory usage: %s\n",mem_usage() if $DEBUG>1;
406
407  printf STDERR "Data loaded\n" if $DEBUG;
408  return(\%Data);
409}
410
411sub LoadGSHHS()
412{
413  my ($Filename, $Bounds) = @_;
414  my @Coasts;
415
416  $Filename =~ s/\~/$ENV{HOME}/;
417
418  if ( ! -s $Filename && -s osm_dir()."/$Filename" ) {
419      $Filename=osm_dir()."/$Filename";
420  }
421  printf "Loading coastlines from GSHHS file %s\n", $Filename;
422  # Open the file for reading, binary 
423  open(my $fp, "<", $Filename)
424      || die("Can't open GSHHS file $Filename ($!)\n");
425  binmode($fp);
426 
427    printf "GSHHS Area Bounds lat %f to %f, long %f to %f\n",
428    $Bounds->{"S"},
429    $Bounds->{"N"},
430    $Bounds->{"W"},
431    $Bounds->{"E"}; 
432
433  # Continue reading headers until end of file
434  while(read($fp, my $header, 8 * 4 + 2 * 2))
435    {
436    my ($Prev, $PLat, $PLon) = (0,0,0);
437    my ($id, $numpoints, $level,
438        $west, $east, $north, $south,
439        $area, $crosses, $source) = unpack("NNNN4Nnn", $header);
440
441    # Loop through vertices in this polygon
442    foreach(1..$numpoints)
443      {
444      # Read from file
445      die("GSHHS error\n") if(!read($fp, my $datapoint, 8));
446
447      # Unpack binary data into local variables
448      my($x, $y) = unpack("NN", $datapoint);
449
450      my $Lat = coastcode($y);
451      my $Lon = coastcode($x);
452      $Lon -= 360 if($Lon > 180);
453
454      if($Lat > $Bounds->{"S"} and 
455        $Lat < $Bounds->{"N"} and 
456        $Lon > $Bounds->{"W"} and 
457        $Lon < $Bounds->{"E"}){
458     
459        push(@Coasts, "$PLat, $PLon, $Lat, $Lon") if($Prev);
460      }
461      ($PLat, $PLon, $Prev) = ($Lat, $Lon, 1);
462      }
463    }
464  close($fp);
465  printf STDERR "Reading GSHHS file $Filename complete\n";
466
467  return(\@Coasts);
468}
469
470sub coastcode($){
471  my $x = shift();
472  if($x > 0x80000000){
473    $x -= 0xFFFFFFFF; $x--;
474  }
475  return($x / 1E+6);
476}
477
478sub LoadOSM($$)
479{
480  my ($Filename, $Bounds) = @_;
481  $Filename =~ s/\~/$ENV{HOME}/;
482
483  # get alittle bit more. This way we increase the chance
484  # of getting segments which are not completely inside too.
485  my $filter = Geo::Filter::Area->new( lat_min => $Bounds->{"S"}-.1,
486                                       lon_min => $Bounds->{"W"}-.1,   
487                                       lat_max => $Bounds->{"N"}+.1,
488                                       lon_max => $Bounds->{"E"}+.1);
489 
490
491
492  printf "Loading OSM Data from %s\n", $Filename;
493  open(my $fp, "<", $Filename) 
494      or die("Can't open $Filename ($!)\n");
495
496  my @Lines;
497  while (  my $line = <$fp> ) {
498      my @col=split(",",$line);
499      next unless $filter->inside({lat=>$col[0],lon=>$col[1]});
500      chomp $line;
501      push (@Lines,$line);
502  }
503  close($fp); 
504
505  return(\@Lines);
506}
507
508
509sub MapPages($$$)
510{
511  my ($PDF, $Options, $Data) = @_;
512 
513  my $Filename = $Options->{"Places"};
514  $Filename =~ s/\~/$ENV{HOME}/;
515  open(my $fp, "<", $Filename) 
516      or die("Can't open $Filename ($!)\n");
517 
518  my @Maps;
519 
520  foreach my $Line(<$fp>){
521      chomp $Line;
522      $Line =~ s/[\r\a\n\s]*$//g;;
523      next if $Line =~ m/^\s*\#/;# Comments starting with #
524      next if $Line =~ m/^\s*$/; # Empty lines
525      push(@Maps, $Line);
526  }
527  printf "Creating %d maps\n", scalar(@Maps);
528 
529  my ($LatC, $LongC, $Size) = split(/,/,$Options->{"Area"});
530  MapContentsPage($PDF, \@Maps, $LatC, $LongC, $Size, $Data);
531 
532  foreach my $Map(@Maps)
533  { 
534      my ($Name, $More) = split(/:\s*/, $Map);
535      my ($Lat, $Long, $Size, $Type) = split(/\s*,\s*/, $More);
536     
537      my $PageNum = $PDF->pages+1;
538      printf STDERR "Generating map for $Name at $Lat, $Long , +- $Size Page  $PageNum\n";
539      MapPage($PDF, $Lat, $Long, $Size, $Name, $Type, $Data);
540
541      if ( $DEBUG >1 ) {
542          # Save the PDF
543          my $pdf_filename=$ResultDir."/".$Options->{"Filename"};
544          #$pdf_filename =~s/\.pdf/-debug.pdf/;
545          printf STDERR "Saving %s\n", $pdf_filename;
546          $PDF->saveas($pdf_filename);
547          $PDF = PDF::API2->open($pdf_filename);
548      }
549  }
550  return $PDF;
551}
552
553sub BBoxPages($)
554{
555  my ($Options) = @_;
556 
557  my $Filename = $Options->{"Places"};
558  $Filename =~ s/\~/$ENV{HOME}/;
559  open(my $fp, "<", $Filename) 
560      or die("Can't open $Filename ($!)\n");
561 
562  my $lat_min =  90;
563  my $lat_max = -90;
564  my $lon_min =  180;
565  my $lon_max = -180;
566
567  foreach my $Line(<$fp>){
568      chomp $Line;
569      $Line =~ s/[\r\a\n\s]*$//g;;
570      next if $Line =~ m/^\s*\#/;# Comments starting with #
571      next if $Line =~ m/^\s*$/; # Empty lines
572      $Line=~ s/^.+\:\s*//;
573      my ($Lat, $Lon, $Size, $Type) = split(/\s*,\s*/, $Line);
574      for my $size ( ($Size,-$Size)){
575          $lat_min  = $Lat+$size  if $lat_min > $Lat+$size;
576          $lat_max  = $Lat+$size  if $lat_max < $Lat+$size;
577         
578          $lon_min  = $Lon+$size  if $lon_min > $Lon+$size;
579          $lon_max  = $Lon+$size  if $lon_max < $Lon+$size;
580      }
581  }
582
583  my $LatC=($lat_min+$lat_max)/2;
584  my $LonC=($lon_min+$lon_max)/2;
585  my $Size=max($lat_max-$lat_min,$lon_max-$lon_min)/2;
586  printf "BBox   $Options->{Area} --> ($LatC,$LonC,$Size) for maps\n";
587  $Options->{"Area"}="$LatC,$LonC,$Size";
588  close $fp;
589
590}
591
592#---------------------------------------------------------
593# Adds a simple "text-style" contents page
594#---------------------------------------------------------
595sub ContentsPage($$)
596{
597        my ($PDF, $Maps) = @_;
598        my $Page = $PDF->page;
599        my $TextSize = 4.5;
600
601        my $PageNum = $PDF->pages + 1; # Assume first map is on page after the current one
602        my $TextHandler = $Page->text;
603       
604        # Title
605        $TextHandler->fillcolor('black');
606        $TextHandler->font($PDF->corefont('Helvetica'), 7/mm );
607        $TextHandler->translate(40/mm, 232/mm);
608        $TextHandler->text("Contents:");
609
610        # Setup size and position of contents items
611        $TextHandler->font($PDF->corefont('Helvetica'), $TextSize/mm ); 
612        my $y = 220;
613               
614        foreach my $Map(@$Maps)
615        {
616            my ($Name, $Misc) = split(/:/, $Map);
617           
618            # Name
619            $TextHandler->translate(40/mm, $y/mm);
620            $TextHandler->text($Name);
621           
622            # Page num     
623            $TextHandler->translate(150/mm, $y/mm);
624            $TextHandler->text($PageNum++);
625           
626            $y -= ($TextSize+1);               
627        }
628}
629
630sub MapContentsPage(){
631  my ($PDF, $Maps, $Lat, $Long, $Size, $Data) = @_;
632  my $Proj = SetupProjection($Lat, $Long, $Size*1.1);
633
634  my $Page = $PDF->page;
635  $Page->mediabox(210/mm, 297/mm);
636  my $Font = $PDF->corefont('Helvetica');
637 
638  my $gfx = $Page->gfx;
639  my $text = $Page->text;
640  $text->font($PDF->corefont('Helvetica'), 4/mm );
641         
642  DrawCoastline($Data->{"Coast"}, $Proj, $gfx);
643  DrawOSM($Data->{"Segments"}, $Proj, $gfx, $Data->{"Styles"});
644  #DrawOSM_POI($PDF,$Data, $Proj,  $Data->{"Styles"});
645  PageFrame($PDF, $Proj);
646
647  my $Count = $PDF->pages + 1;
648  my $map_is_on_page=2;
649  foreach my $Map(@$Maps)
650  {
651    my ($Name, $More) = split(/:\s*/, $Map);
652    my ($LatS, $LongS, $SizeS, $Type) = split(/\s*,\s*/, $More);
653    my $IsCity = $Type eq "city";
654    my $Colour = $IsCity ? "#000000" : "#40C0FF";
655   
656    unless ( defined($LatS) && defined($LongS) && defined($SizeS) ) {
657        confess "Map Definition has undefined Values:".Dumper(\$Map)."\n";
658    }
659    my $Proj2 = SetupProjection($LatS, $LongS, $SizeS);
660   
661    my ($x1, $y1) = Project($Proj, $Proj2->{"S"}, $Proj2->{"W"});
662    my ($x2, $y2) = Project($Proj, $Proj2->{"N"}, $Proj2->{"E"});
663   
664    # Map border
665    $gfx->strokecolor($Colour);
666    $gfx->rect($x1/mm, $y1/mm, ($x2-$x1)/mm, ($y2-$y1)/mm); 
667    $gfx->stroke();
668    $gfx->endpath();
669   
670    $text->fillcolor($Colour);
671    # Text (inside area maps, outside city maps)
672    if($IsCity){
673        $text->font($Font, 3/mm );
674        $text->translate(($x1 + 1)/mm, ($y2)/mm);
675        $text->text($Name);
676    } else {
677        $text->font($Font, 4/mm );
678        $text->translate(( $x1 + 1)/mm, ($y2 - 4)/mm);
679        $text->text($Name);
680    }
681
682    $map_is_on_page++;
683    $text->font($Font, 4/mm );
684    $text->translate(($x2-1)/mm, ($y2 - 4)/mm);
685    $text->text_right($Count);
686   
687    $Count++;
688  }
689}
690
691# White out the outside of the map
692sub WhiteOutEdges($$) {
693    my ($PDF, $Proj) = @_;
694
695    my $Page  = $PDF->openpage(0);
696    my $edges = $Page->gfx;
697
698    # White-out the edges of the page (stop maps spilling over)
699    $edges->rect(0/mm, 0/mm, 10/mm, 297/mm); # left
700    $edges->rect(0/mm, 0/mm, 210/mm, 10/mm); # bottom
701    $edges->rect(200/mm, 0/mm, 210/mm, 297/mm); # right
702    $edges->rect(0/mm, 287/mm, 210/mm, 297/mm); # top
703    $edges->fillcolor("#FFFFFF"); 
704    $edges->fill; 
705    $edges->endpath;
706}
707
708sub MapBorder($$) {
709    my ($PDF, $Proj) = @_;
710
711    my $Page  = $PDF->openpage(0);
712    my $edges = $Page->gfx;
713   
714    # Map border
715    $edges->strokecolor("#000080");
716    $edges->rect(10/mm, 10/mm, 190/mm, 277/mm); 
717    $edges->stroke();
718    $edges->endpath();
719
720}
721
722# Add a page Frame with
723#   Page Numbers, URL and Name
724sub PageFrame($$) {
725    my ($PDF, $Proj) = @_;
726
727    WhiteOutEdges($PDF, $Proj);
728    MapBorder($PDF, $Proj);
729
730    my $Page  = $PDF->openpage(0);
731    my $Font = $PDF->corefont('Helvetica');
732    my $text = $Page->text; 
733
734    # Page number
735    $text->fillcolor("#000000"); 
736    $text->font($Font, 4/mm );
737    $text->translate( 200/mm, 6/mm );
738    $text->text_right(sprintf("%d", $PDF->pages));
739
740    # URL
741    $text->font($Font, 4/mm );
742    $text->translate( 10/mm, 6/mm );
743    $text->text("http://openstreetmap.org/  © 2002-2007 CC-by-SA 2.0 license");
744}
745
746#---------------------------------------------------------
747# Adds a page of maps
748#---------------------------------------------------------
749sub MapPage(){
750  my ($PDF, $Lat, $Long, $Size, $Name, $Type, $Data) = @_;
751
752  my $Proj = SetupProjection($Lat, $Long, $Size);
753
754  my $Page = $PDF->page;
755  $Page->mediabox(210/mm, 297/mm);
756 
757  my $gfx = $Page->gfx;
758  my $Font = $PDF->corefont('Helvetica');
759
760  # Draw the "simple" (A1 - G9) grid
761  mapGrid($Page, 10, 287, 200, 10, $Font, $Proj);
762
763  DrawCoastline($Data->{"Coast"}, $Proj, $gfx);
764
765  DrawOSM($Data->{"Segments"}, $Proj, $gfx, $Data->{"Styles"});
766  #DrawOSM_POI($PDF,$Data, $Proj,  $Data->{"Styles"});
767
768  ScaleBar($PDF, $Page, $Proj);
769 
770  PageFrame($PDF, $Proj);
771 
772  my $text = $Page->text; 
773  $text->fillcolor("#000000"); 
774
775  # Page name
776  $text->font($Font, 6/mm );
777  $text->translate( 200/mm, 289/mm );
778  $text->text_right($Name);
779
780}
781
782#---------------------------------------------------------
783# Draw coastline segments       
784#---------------------------------------------------------
785sub DrawCoastline()
786{
787  my($Coast, $Proj, $gfx) = @_;
788
789  if($Coast)
790  {
791    foreach my $Line(@$Coast)
792    {
793      my ($Lat1, $Long1, $Lat2, $Long2) = split(/,/, $Line);
794      if(PossiblyOn($Proj, $Lat1, $Long1, $Lat2, $Long2))
795      {
796        my ($x1, $y1) = Project($Proj, $Lat1, $Long1);
797        my ($x2, $y2) = Project($Proj, $Lat2, $Long2);
798         
799        my $Colour = "#0000FF";
800       
801        $gfx->strokecolor($Colour);
802        $gfx->move($x1/mm,$y1/mm);
803        $gfx->line($x2/mm,$y2/mm); 
804        $gfx->stroke();
805        $gfx->endpath();
806      }
807    }
808  }     
809}
810
811#---------------------------------------------------------
812# Draw OpenStreetMap POI
813#---------------------------------------------------------
814sub DrawOSM_POI()
815{
816    my($PDF,$Data, $Proj, $Styles) = @_;
817    my $Points=$Data->{"Points"};
818    my $MapFeatures = $Data->{map_features};
819    if($Points)
820    {
821        my $Page = $PDF->openpage(0);
822        my $gfx = $Page->gfx;
823        my $text = $Page->text; 
824        $text->font($PDF->corefont('Helvetica'), 4/mm );
825        my $scale = Scale($Proj);
826       
827        foreach my $Line(@$Points)
828        {
829            my($Lat1,$Long1,$Name,$Type) = split(/,/,$Line,4);
830            unless ( $Lat1 && $Long1 ) {
831                confess "$Line\nany of lat/lon is 0\n";
832            }
833           
834           
835            $Type = lc($Type); 
836            if(PossiblyOn($Proj, $Lat1, $Long1, $Lat1, $Long1))
837            {
838                my ($x1, $y1) = Project($Proj, $Lat1, $Long1);
839               
840                my $Colour = exists($Styles->{$Type}) ? $Styles->{$Type}: "#A0A0A0";
841                $text->fillcolor($Colour); 
842               
843                $gfx->move($x1/mm,$y1/mm);
844                $text->translate(($x1-2)/mm, ($y1-2)/mm );
845
846                print STDERR "Draw_OSM():$Line, $Type\n" 
847                    if $DEBUG>1;
848
849                my $img = $MapFeatures->get_icons($Type,$scale);
850                if ( my $img ) {
851                    $gfx-> image($img, 
852                                 $x1/mm - ($img->width/2),
853                                 $y1/mm - ($img->height/2));
854                }
855
856                $text->text("x  $Name"); #,$Type");
857            }
858        }
859    }
860}
861#---------------------------------------------------------
862# Draw a basic (uncorrelated with anything) grid on a map
863#---------------------------------------------------------
864sub mapGrid($$$$$$$)
865{
866  my ($Page, $x1, $y1, $x2, $y2, $Font, $Proj) = @_;
867  my $grid = $Page->gfx;
868 
869  my $xLabels = "ABCDEFG";
870  my $dx = ($x2 - $x1) / 7;
871  my $dy = ($y2 - $y1) / 10;
872 
873  my $gridtext = $Page->text; 
874  $gridtext->fillcolor("#C1E4FF"); 
875  $gridtext->font($Font, 3/mm );
876 
877  my $count = 0;
878  for(my $x = $x1; $x < $x2-1; $x += $dx)
879  {
880    $grid->move($x/mm,$y1/mm);
881    $grid->line($x/mm,$y2/mm); 
882   
883    $gridtext->translate(($x + $dx - 4)/mm, ($y1 - 4)/mm );
884    $gridtext->text(substr($xLabels, $count++, 1));
885  }
886 
887  $count = 1;
888  for(my $y = $y1; $y > $y2+1; $y += $dy)
889  {
890    $grid->move($x1/mm,$y/mm);
891    $grid->line($x2/mm,$y/mm); 
892   
893    $gridtext->translate(($x1 + 2)/mm, ($y + $dy + 2)/mm );
894    $gridtext->text($count++);
895   
896  }
897 
898  $grid->strokecolor("#C1E4FF");
899  $grid->stroke();
900  $grid->endpath();
901}
902#---------------------------------------------------------
903# Draw OpenStreetMap segments
904#---------------------------------------------------------
905sub DrawOSM()
906{
907  my($Segments, $Proj, $gfx, $Styles) = @_;
908 
909  if($Segments)
910  {
911    foreach my $Line(@$Segments)
912    {
913      my($Lat1,$Long1,$Lat2,$Long2,$Class,$Name,$Highway) = split(/,/,$Line);
914      unless ( $Lat1 && $Long1 && $Lat2 && $Long2 ) {
915          confess "$Line\nany of lat/lon is 0\n";
916      }
917     
918      $Class = $Highway if(!$Class);
919     
920
921      $Class = lc($Class); 
922      if(PossiblyOn($Proj, $Lat1, $Long1, $Lat2, $Long2))
923      {
924        my ($x1, $y1) = Project($Proj, $Lat1, $Long1);
925        my ($x2, $y2) = Project($Proj, $Lat2, $Long2);
926 
927        my $Colour = exists($Styles->{$Class}) ? $Styles->{$Class}: "#A0A0A0";
928 
929        $gfx->strokecolor($Colour);
930        $gfx->move($x1/mm,$y1/mm);
931        $gfx->line($x2/mm,$y2/mm); 
932        $gfx->stroke();
933        $gfx->endpath();
934      }
935    }
936  }
937}
938
939#---------------------------------------------------------
940# Draw a scalebar on a map
941#---------------------------------------------------------
942sub ScaleBar()
943{
944  my ($PDF, $Page, $Proj) = @_;
945  my $EarthRadius = 6378;
946 
947  my $ScaleX = 20;
948  my $ScaleY = 20;
949  my $dx = 100;
950 
951  my $ScaleLenDeg = $Proj->{"dLong"} * $dx / $Proj->{"dx"};
952  my $ScaleLenGuess = $EarthRadius * DegToRad($ScaleLenDeg) * $Proj->{"latlong_ratio"};
953 
954  my $ScaleLen = Round($ScaleLenGuess);
955  $dx *= $ScaleLen / $ScaleLenGuess;
956 
957  my $scale = $Page->gfx;
958 
959  # Bordered rectangle, to get rid of any bits of map under the scalebar
960  BorderRect($scale,
961    ($ScaleX - 2)/mm, 
962    ($ScaleY - 8)/mm, 
963    ($dx + 4)/mm, 
964    10/mm,
965    "#FFFFFF", 
966    "#CCCCFF");
967 
968  # Scalebar length
969  $scale->move($ScaleX/mm, $ScaleY/mm);
970  $scale->line(($ScaleX + $dx)/mm, $ScaleY/mm); 
971  # and tickmarks
972  foreach(0..10){
973    my $Len = (($_ % 5) == 0) ? 2 : 1;
974    my $X = $ScaleX + $dx * $_ / 10;
975    $scale->move($X/mm, $ScaleY/mm);
976    $scale->line($X/mm, ($ScaleY - $Len)/mm);
977  }
978 
979  $scale->strokecolor("#000000");
980  $scale->stroke();
981 
982  # Scalebar text
983  my $text = $Page->text; 
984  $text->fillcolor("#000000"); 
985  $text->font($PDF->corefont('Helvetica'), 4/mm );
986  $text->translate($ScaleX/mm, ($ScaleY - 6)/mm );
987  $text->text(FormatNum($ScaleLen) . " km");
988  $text->translate($ScaleX/mm  +$dx/mm, ($ScaleY - 6)/mm );
989  $text->text_right(" 1/".FormatNum(int(Scale($Proj))));
990
991}
992
993#---------------------------------------------------------
994# Draw a rectangle with fill and border
995#---------------------------------------------------------
996sub BorderRect()
997{
998  my ($gfx, $x, $y, $w, $h, $fill, $line) = @_;
999  $gfx->rect($x, $y, $w, $h);
1000  $gfx->fillcolor($fill); 
1001  $gfx->fill; 
1002 
1003  $gfx->rect($x, $y, $w, $h);
1004  $gfx->strokecolor($line);
1005  $gfx->stroke();
1006}
1007
1008#---------------------------------------------------------
1009# Format a number without trailing zeros
1010#---------------------------------------------------------
1011sub FormatNum()
1012{
1013  my $Text = sprintf("%lf", shift());
1014  $Text =~ s/(\.\d*?)0+$/$1/;
1015  $Text =~ s/\.$//;
1016  return($Text);
1017}
1018
1019#---------------------------------------------------------
1020# Utility: round a number to the nearest power of 10
1021#---------------------------------------------------------
1022sub Round()
1023{
1024  my $x = shift();
1025  my $Result = $x;
1026  foreach(0.1, 0.5, 1, 2, 5, 10, 50, 100){
1027    $Result = $_ if($x > $_);
1028    }
1029  return($Result);
1030}
1031
1032#---------------------------------------------------------
1033# Tests whether a line can possibly intersect an area
1034#---------------------------------------------------------
1035sub PossiblyOn()
1036{
1037  my ($Proj, $Lat1, $Long1, $Lat2, $Long2) = @_;
1038  return(0) if($Long1 < $Proj->{"W"} && $Long2 < $Proj->{"W"});
1039  return(0) if($Long1 > $Proj->{"E"} && $Long2 > $Proj->{"E"});
1040  return(0) if($Lat1 < $Proj->{"S"} && $Lat2 < $Proj->{"S"});
1041  return(0) if($Lat1 > $Proj->{"N"} && $Lat2 > $Proj->{"N"});
1042  return(1);
1043}
1044
1045#---------------------------------------------------------
1046# get back Scale for a given Projection
1047#---------------------------------------------------------
1048sub Scale($)
1049{
1050  my ($Proj) = @_;
1051  if ( $Proj->{"dLong"} ==0  ) {
1052      cluck "Proj-dLong =0\n";
1053      return undef;
1054  }
1055  if ( $Proj->{"dLat"} ==0  ) {
1056      cluck "Proj-dat =0\n";
1057      return undef;
1058  }
1059  # TODO:
1060  # This is not the correct Formula, but for now it
1061  # gives a wild guess which is good enough
1062  my $scale = max($Proj->{"dLat"},$Proj->{"dLong"}); # degrees per page
1063  $scale=40*1000*1000*100/360*$scale; # cm in reality/page
1064  $scale=$scale/30; #  1/$scale for the page
1065  #print "Scale: $scale\n";
1066  return $scale;
1067}
1068
1069#---------------------------------------------------------
1070# Project a lat/long onto x,y coordinates
1071#---------------------------------------------------------
1072sub Project($$$)
1073{
1074  my ($Proj, $Lat, $Long) = @_;
1075
1076
1077  if ( $Proj->{"dLong"} ==0  ) {
1078      cluck "Proj-dLong =0\n";
1079      return (0,0);
1080  }
1081  if ( $Proj->{"dLat"} ==0  ) {
1082      cluck "Proj-dat =0\n";
1083      return (0,0);
1084  }
1085  my $x = $Proj->{"x1"} + $Proj->{"dx"} * ($Long - $Proj->{"W"}) / $Proj->{"dLong"};
1086  my $y = $Proj->{"y1"} + $Proj->{"dy"} * ($Lat - $Proj->{"S"}) / $Proj->{"dLat"};
1087 
1088  return($x,$y);
1089}
1090
1091#---------------------------------------------------------
1092# Initialise a map projection for later use by Project()
1093#---------------------------------------------------------
1094sub SetupProjection($$$)
1095{
1096  my ($Lat, $Long, $Size) = @_;
1097  my %Proj;
1098 
1099  # Scale to A4 page (note: A4 page sizes are hardcoded throughout at the moment)
1100  $Proj{"x1"} = 10;
1101  $Proj{"x2"} = 200;
1102  $Proj{"y1"} = 10;
1103  $Proj{"y2"} = 287;
1104
1105  $Proj{"dx"} = $Proj{"x2"} - $Proj{"x1"};
1106  $Proj{"dy"} = $Proj{"y2"} - $Proj{"y1"};
1107
1108  $Proj{"page_ratio"} = $Proj{"dy"} / $Proj{"dx"};
1109
1110  # Size is used to define N-S limits
1111  $Proj{"N"} = $Lat + $Size;
1112  $Proj{"S"} = $Lat - $Size;
1113  $Proj{"dLat"} = $Proj{"N"} - $Proj{"S"};
1114
1115  # Long/lat ratio is cos(lat) (very simple "projection")
1116  $Proj{"latlong_ratio"} = cos(DegToRad($Lat)); # ~0.5 for northern europe = long/lat
1117
1118  confess("Projection D-Lat=0 for SetupProjection($Lat,$Long,$Size)\n")
1119          unless $Proj{"dLat"};
1120  confess("Page ratio is =0 for SetupProjection($Lat,$Long,$Size)\n")
1121      unless $Proj{"page_ratio"};
1122  confess("latlong ratio is =0 for SetupProjection($Lat,$Long,$Size)\n")
1123      unless $Proj{"latlong_ratio"};
1124         
1125  $Proj{"dLong"} = $Proj{"dLat"} * $Proj{"page_ratio"} * $Proj{"latlong_ratio"};
1126  confess("Projection D-Lon =0 for SetupProjection($Lat,$Long,$Size)\n")
1127          unless $Proj{"dLong"};
1128
1129  $Proj{"W"} = $Long - 0.5 * $Proj{"dLong"};
1130  $Proj{"E"} = $Long + 0.5 * $Proj{"dLong"};
1131 
1132  return(\%Proj);
1133}
1134sub DegToRad(){
1135 return(3.1415926 * shift() / 180);
1136}
1137#---------------------------------------------------------
1138# Reads a file consisting of "Name: Value" pairs, and
1139# returns them as a hash
1140#---------------------------------------------------------
1141sub ReadFile($){
1142  my $Filename = shift();
1143
1144  $Filename =~ s/\~/$ENV{HOME}/;
1145  my %Options;
1146  printf STDERR "\n-----------------------------------\n" if $DEBUG>1;
1147  printf STDERR "  - Reading %s\n", $Filename;
1148  open(my $fp, $Filename) 
1149      || die("Can't open $Filename ($!)\n");
1150 
1151  foreach my $Line(<$fp>){
1152      chomp $Line;
1153      $Line =~ s/[\r\a\n\s]*$//g;;
1154      next if $Line =~ m/^\s*\#/;# Comments starting with #
1155      next if $Line =~ m/^\s*$/; # Empty lines
1156      my ($Key, $Value) = split(/:\s*/, $Line,2);
1157      chomp $Value;
1158      $Value =~ s/[\r\a\n\s]*$//g;;
1159      print STDERR "Key: $Key   Value:$Value\n" 
1160          if $DEBUG>5;
1161      $Options{$Key} = $Value;
1162      unless ( $Key && defined($Value) && $Value ) { 
1163          warn "Key:Value split not successfullt $Key:$Value"
1164          };
1165  }
1166  close $fp;
1167  printf STDERR "Options($Filename):".Dumper(\%Options)."\n" if $DEBUG > 10;
1168  printf STDERR "File $Filename read complete\n" if $DEBUG >1;
1169  printf STDERR "+++++++++++++++++++++++++++++++++++++++++\n" if $DEBUG > 2;
1170  return(\%Options);
1171}
1172
1173##################################################################
1174# Usage/manual
1175
1176__END__
1177
1178=head1 NAME
1179
1180B<pdf-atlas.pl> Version 0.02
1181
1182=head1 DESCRIPTION
1183
1184B<pdf-atlas.pl> is a program to create an atlas
1185The output is a PDF File
1186The data is taken from a osm-file which is converted to a csv file.
1187
1188=head1 SYNOPSIS
1189
1190B<Common usages:>
1191
1192pdf-atlas.pl [-d] [-v] [-h]
1193                 [-config=<xx.txt>] [--Places=<xx.txt>]
1194                 [--ResultDir=<dir>] [--no-png] [--no-html]
1195
1196=head1 OPTIONS
1197
1198=over 2
1199
1200=item B<--man> Complete documentation
1201
1202Complete documentation
1203
1204=item B<-c|--config> <config.txt> use config.txt
1205
1206The default is Config/config.txt
1207
1208=item B<--Places> <places.txt> use places.txt
1209
1210use places.txt for the list of places to add
1211
1212=item B<--ResultDir> optional output dir
1213
1214write the results to this dir.
1215The Default is ~/osm/pdf-atlas/
1216
1217=item B<--Data>=<optional Data File>
1218
1219Where is the csv Data read from instead of
1220~/osm/planet/csv/osm.csv
1221
1222=item B<--force-update>
1223
1224Normally the result File is only updated if the timestamp
1225of the datafile is newer than the
1226Output pdf File
1227
1228=item B<--no-png>
1229
1230Normally the resulting PDF-File is also converted with 'convert' to png
1231Files. This makes building an html index and viewing much easier.
1232With this option set no conversion will be done.
1233
1234=item B<--no-html>
1235
1236Normally the resulting PDF-File is also converted with 'convert' to png
1237Files. This makes building an html index and viewing much easier.
1238With this option set no conversion will be done.
1239
1240=back
1241
1242=head1 TODO
1243
1244 - Page content a little smaller, so it always fits on A4
1245 - Add Page Numbers to overview
1246 - Add Main Streets to overview
1247 - Overview on Titlepage (optional)
1248 - Add City Names to output from places.txt
1249 - Add POI from places.txt with icons defined in *.XML scheme
1250 - Option to automagically start convert after creating pdf
1251 - make configfile for places more flexible: for this change order
1252        area,<lat>,<lon>,<size>,<name>
1253
1254
1255=head1 COPYRIGHT
1256
1257 Copyright 2006, OJW
1258 Copyright 2006, Jörg Ostertag
1259
1260This program is free software; you can redistribute it and/or
1261modify it under the terms of the GNU General Public License
1262as published by the Free Software Foundation; either version 2
1263of the License, or (at your option) any later version.
1264
1265This program is distributed in the hope that it will be useful,
1266but WITHOUT ANY WARRANTY; without even the implied warranty of
1267MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1268GNU General Public License for more details.
1269
1270You should have received a copy of the GNU General Public License
1271along with this program; if not, write to the Free Software
1272Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
1273
1274=head1 AUTHOR
1275
1276OJW <streetmap@blibbleblobble.co.uk>,
1277Jörg Ostertag (planet-count-for-openstreetmap@ostertag.name)
1278
1279=head1 SEE ALSO
1280
1281osm2csv.pl
1282
1283http://www.openstreetmap.org/
1284
1285=cut
Note: See TracBrowser for help on using the repository browser.