#!/usr/bin/perl -w

# osm2ai.pl
# Create Adobe Illustrator 6 file from OpenStreetMap data
# changed to create plain Postscript by stw

# Richard Fairhurst/editions Systeme D 2007-8
# distributed under the terms of the WTFPL
# http://sam.zoy.org/wtfpl/COPYING

# Options:
# --output       output filename

# and either:
# --input       input filename
# or:
# --xmin, --xmax, --ymin, --ymax bounding box
# --projection      mercator or osgb
# --db, --user, --password   database connection (will use environment variables if present)

# ----------------------------------------------------------------
# To do (also see 'limitations'):
# - option to generate grid (OSGB) or use fixed scale
# - instead of bounding box, --place=Oxford, --radius=20
# - optional join on the way_tags table to reduce number of ways returned

# ================================================================
# Initialise

#use DBI;
use Math::Trig;
use Getopt::Long;
use Pod::Usage;
#use Geo::Coordinates::OSGB qw(ll_to_grid);

# ================================================================
# User-defined variables

$dbname='openstreetmap'; if (exists $ENV{'DBNAME'}) { $dbname=$ENV{'DBNAME'}; };
$dbuser='openstreetmap'; if (exists $ENV{'DBUSER'}) { $dbname=$ENV{'DBUSER'}; };
$dbpass='openstreetmap'; if (exists $ENV{'DBPASS'}) { $dbname=$ENV{'DBPASS'}; };

$xmin=-3.7; $xmax= 1.9; $ymin=50.5 ; $ymax=54.3; # $xmin=-2.3; $xmax=-2.2; $ymin=52.15; $ymax=52.25;
$proj='mercator';         # $proj='osgb';
$outfile='output.ai';
$infile='';
$help=$man=0;

GetOptions('xmin=f' =>\$myxmin, 'xmax=f' =>\$myxmax,
     'ymin=f' =>\$myymin, 'ymax=f' =>\$myymax,
     'projection=s' =>\$proj, 'help|?' => \$help, man => \$man,
     'input=s' =>\$infile, 'output=s' =>\$outfile,
     'filter=s' =>\$filter,
     'password=s' =>\$dbpass, 'user=s' =>\$dbuser, 'db=s' =>\$dbname);
pod2usage(1) if $help;
pod2usage(-exitstatus => 0, -verbose => 2) if $man;
$proj=lc $proj;

# ================================================================
# Read ways from file

if ($infile) {
 open INFILE,$infile or die "Can't open input file: $!\n";
 $way=0;
 $xmin=99999; $xmax=-99999;
 $ymin=99999; $ymax=-99999;
 $bounds=0;
 %lat=(); %lon=();
 %paths=(); # hash of arrays
 %attributes=(); # hash of hashes
 %nattributes=(); # hash of hashes for the nodes
 print "Reading input file\n";
 while (<INFILE>) {
  chomp ($a=$_);
  if ($a=~/<node id=['"](-?\d+)['"] .*lat=['"]([\d.\-]+)['"] lon=['"]([\d.\-]+)['"]/) {
   $node=$1;
   $lat{$1}=$2; $lon{$1}=$3;
   if(!$bounds){
    if ($2<$ymin) { $ymin=$2; }
    if ($2>$ymax) { $ymax=$2; }
    if ($3<$xmin) { $xmin=$3; }
    if ($3>$xmax) { $xmax=$3; }
   }
   %attribute=();
  } elsif ($a=~/<\/node>/) {
   if(!$filter){
    $nattributes{$node}={%attribute};
   }
   $node=0;

  } elsif ($a=~/<way id=['"](-?\d+)['"]/) {
   if($filter){
    $way=$1;
    @path=(); %attribute=();
    if($a=~/user=['"]$filter['"]/ || ($filter eq "new" && !($a=~/user=/))){
     push @waylist,$way;
    }else{
     $way=0;
    }
   }else{
    $way=$1;
    @path=(); %attribute=();
    push @waylist,$way;
   }
  } elsif ($way && $a=~/<\/way>/) {
   $paths{$way}=[@path];
   $attributes{$way}={%attribute};
   $way=0;
   # set path and attributes
   # write to waylist

  } elsif ($a=~/<nd ref=['"](-?\d+)['"]/) {
   push @path,$1;
  } elsif (($way || $node) && $a=~/<tag k=['"](.+?)['"] v=['"](.+?)['"]/) {
   $attribute{$1}=$2;
  } elsif ($a=~/<bounds +minlat=['"]([0-9.]*)['"] +minlon=['"]([0-9.]*)['"] +maxlat=['"]([0-9.]*)['"] +maxlon=['"]([0-9.]*)['"]/) {
   $ymin=$1; $xmin=$2;
   $ymax=$3; $xmax=$4;
   $bounds=1;
  }
 }
 close INFILE;

 if($myxmin){ $xmin=$myxmin; };
 if($myxmax){ $xmax=$myxmax; };
 if($myymin){ $ymin=$myymin; };
 if($myymax){ $ymax=$myymax; };

} else {
# ================================================================
# Read ways from database

 $dbh=DBI->connect("DBI:mysql:$dbname",$dbuser,$dbpass, { RaiseError =>1 } );
 print "Getting list of ways\n";
 @waylist=Which_Ways();
}

# ================================================================
# Generate file

# ----- Set scale

if ($proj eq 'osgb') {
 ($basex,$basey)=ll_to_grid($ymin,$xmin); $masterscale=1/250;
} else {
 $mmx=594;
 $baselong=$xmin; $basey=lat2y($ymin);
 $masterscale=$mmx/($xmax-$xmin);
 $mmy = (lat2y($ymax) - $basey) * $masterscale;
 $mmy += 10;
}

# ----- Read all ways

$i=0; $al=@waylist;
foreach $way (@waylist) {
 $i++;
 print "Reading way $i of $al\r";
 if ($infile) {
  $newpath=[];          # Project
  $inview=0;
  foreach $id (@{$paths{$way}}) {
   if($lon{$id} && $lat{$id}){
    if ($proj eq 'mercator') { $xs=long2coord($lon{$id}); $ys=lat2coord($lat{$id}); }
    elsif ($proj eq 'osgb')  { ($xs,$ys)=ll2osgb($lon{$id},$lat{$id}); }
    push @{$newpath},[$xs,$ys,$id];
    if(!$inview && $xs>0 && $xs<$mmx && $ys>-10 && $ys<$mmy){
     $inview=1;
    }
   }
  }
  if($inview){
   $paths{$way}=$newpath;
  }else{
   $attribute{$way}=0;
   $paths{$way}=0;
  }
 } else {
  ($path,$attribute)=Get_Way($way);     # Read the way
  $paths{$way}=$path;
  $attributes{$way}=$attribute;
 }
}

# ----- Write file

open (OUTFILE, ">$outfile") or die "Can't open output file: $!";
#| iconv -f utf8 -t latin1 

foreach $way (@waylist) {
 if($attributes{$way}){
  Parse_Path($way);
 }else{
  $addinfo{$way}=0;
 }
}

print "Writing file                               \n";
Output_Header();
foreach $printlayer (0,1,2,3,4,5) {
 print OUTFILE "\n\% Layer: $printlayer\n";
 foreach $way (@waylist) {
  if($paths{$way} && $addinfo{$way} && $addinfo{$way}[3] == $printlayer) {
   $path=$paths{$way};
   $type=$addinfo{$way}[0];
   $stroke=$addinfo{$way}[1];
   if($stroke eq "DS"){ $stroke="DSo"; }
   $info=$addinfo{$way}[2];
   print OUTFILE "\%$info\n";
   $point='m';
   $minx=$mmx;
   $maxx=0;
   $miny=$mmy;
   $maxy=-10;
   foreach $row (@{$path}) {
    print OUTFILE "$row->[0] $row->[1] $point\n"; $point='l';
    if($row->[0]<$minx){ $minx=$row->[0]; }
    if($row->[1]<$miny){ $miny=$row->[1]; }
    if($row->[0]>$maxx){ $maxx=$row->[0]; }
    if($row->[1]>$maxy){ $maxy=$row->[1]; }
   }
   if($stroke eq "mil"){
    $maxx-=$minx;
    $maxy-=$miny;
    $stroke="$minx $miny $maxx $maxy $stroke";
   }
   print OUTFILE "$type $stroke reset\n";
  }
 }
 foreach $way (@waylist) {
  if($paths{$way} && $addinfo{$way} && $addinfo{$way}[3] == $printlayer && $addinfo{$way}[1] eq "DS") {
   $path=$paths{$way};
   $type=$addinfo{$way}[0];
   $stroke="DSi";
   $info=$addinfo{$way}[2];
   print OUTFILE "\%$info\n";
   $point='m';
   foreach $row (@{$path}) {
    print OUTFILE "$row->[0] $row->[1] $point\n"; $point='l';
   }
   print OUTFILE "$type $stroke reset\n";
  }
 }
}

# --- write POIs ---

$fontsize=4;
if(!$filter){
 print OUTFILE "\n\% POIs\n";
 foreach $node (keys %nattributes) {
  $name=$nattributes{$node}{"name"};
  if($name){
   $id=$node;
   if ($proj eq 'mercator') { $xs=long2coord($lon{$id}); $ys=lat2coord($lat{$id}); }
    elsif ($proj eq 'osgb')  { ($xs,$ys)=ll2osgb($lon{$id},$lat{$id}); }
   if($xs>=0 && $ys>=0){
    $size=1;
    if($nattributes{$node}{"traffic_sign"} &&
       $nattributes{$node}{"traffic_sign"} eq "city_limit"){
     print OUTFILE "0.7 0.7 0 setrgbcolor\n";
    }
    if($nattributes{$node}{"place"} &&
       $nattributes{$node}{"place"} eq "town"){
     print OUTFILE "0 0.6 0 setrgbcolor\n";
     $fontsize=8;
     $size=0;
    }
    if($nattributes{$node}{"place"} &&
       $nattributes{$node}{"place"} eq "region"){
     print OUTFILE "0.6 0.6 0 setrgbcolor\n";
     $fontsize=6;
     $size=0;
    }
    if($nattributes{$node}{"place"} && (
       $nattributes{$node}{"place"} eq "suburb"||
       $nattributes{$node}{"place"} eq "village"||
       $nattributes{$node}{"place"} eq "hamlet"||
       0)){
     print OUTFILE "0 0.6 0 setrgbcolor\n";
     $fontsize=6;
     $size=0;
    }
    if($size){
     print OUTFILE "$xs $ys m dot reset\n";
    }
    if($fontsize!=4){
     print OUTFILE "(Helvetica) findfont $fontsize scalefont setfont\n";
    }
    if(! $label[int($xs/20)][int($ys/10)]){
     $label[int($xs/20)][int($ys/10)] = 1;
     $name =~ s/ä/ae/g;
     $name =~ s/ö/oe/g;
     $name =~ s/ü/ue/g;
     $name =~ s/Ä/Ae/g;
     $name =~ s/Ö/Oe/g;
     $name =~ s/Ü/Ue/g;
     $name =~ s/ß/ss/g;
     $name =~ s/\&quot;/"/g;
     $name =~ s/[éè]/e/g;
     $name =~ s/\&amp;/\&/g;
     print OUTFILE "$xs $ys m ($name) 0 Cshow\n";
    }
    print OUTFILE "reset\n";
    if($fontsize!=4){
     $fontsize=4;
     print OUTFILE "(Helvetica) findfont $fontsize scalefont setfont\n";
    }
   }
  }
 }
}

# --- write names ---

print OUTFILE "\% names\n";
foreach $way (@waylist) {
 if($paths{$way} && $addinfo{$way} && $addinfo{$way}[4]) {
  $path=$paths{$way};
  $info=$addinfo{$way}[2];
  $name=$addinfo{$way}[4];
  $lastx=1000;
  $lasty=0;
  $beforex=$beforey=0;
  for $row (@{$paths{$way}}){
   $xs=$row->[0];
   $ys=$row->[1];
   if($beforex){
    $alpha = atan2($ys-$beforey, $xs-$beforex);
    $alpha *= 180/pi;
    if($alpha>90){ $alpha-=180; }
    if($alpha<=-90){ $alpha+=180; }
    $xs=($xs+$beforex)/2;
    $ys=($ys+$beforey)/2;
    $dist=($xs-$lastx)**2 + ($ys-$lasty)**2;
    if($dist>40**2 && $xs>=0 && $ys>=0){
     if(! $label[int($xs/20)][int($ys/10)]){
      $label[int($xs/20)][int($ys/10)] = 1;
      print OUTFILE "\%$info\n";
      print OUTFILE "$xs $ys m ($name) $alpha Cshow\n";
      $lastx=$xs;
      $lasty=$ys;
     }
    }
   }
   $beforex=$row->[0];
   $beforey=$row->[1];
  }
 }
}

Output_Footer();
close OUTFILE;
unless ($infile) { $dbh->disconnect(); }



# ================================================================
# OSM database routines

# ----- Which_Ways
#  returns array of ways

sub Which_Ways {
 my $tilesql=sql_for_area($ymin,$xmin,$ymax,$xmax,'');
 $symin=$ymin*10000000; $symax=$ymax*10000000;
 $sxmin=$xmin*10000000; $sxmax=$xmax*10000000;
 my $sql=<<EOF;
SELECT DISTINCT current_way_nodes.id AS wayid
  FROM current_way_nodes,current_nodes,current_ways
 WHERE current_nodes.id=current_way_nodes.node_id
   AND current_nodes.visible=1
   AND current_ways.id=current_way_nodes.id
   AND current_ways.visible=1
   AND ($tilesql)
   AND (latitude  BETWEEN $symin AND $symax)
   AND (longitude BETWEEN $sxmin AND $sxmax)
 ORDER BY wayid
EOF
 my $query=$dbh->prepare($sql);
 my @ways=();
 $query->execute();
 while ($wayid=$query->fetchrow_array()) { push @ways,$wayid; }
 $query->finish();
 return @ways;
}

# ----- Get_Way(id)
#  returns path array, attributes hash

sub Get_Way {
 my $wayid=$_[0];
 my ($lat1,$long1,$id1,$lat2,$long2,$id2,$k,$v);
 my $sql=<<EOF;
SELECT latitude*0.0000001,longitude*0.0000001,current_nodes.id
  FROM current_way_nodes,current_nodes
 WHERE current_way_nodes.id=?
   AND current_way_nodes.node_id=current_nodes.id
   AND current_nodes.visible=1
 ORDER BY sequence_id
EOF
 my $path=[];
 my $query=$dbh->prepare($sql);
 $query->execute($wayid);

 while (($lat,$long,$id)=$query->fetchrow_array()) {
  if ($proj eq 'mercator') { $xs=long2coord($long); $ys=lat2coord($lat); }
  elsif ($proj eq 'osgb')  { ($xs,$ys)=ll2osgb($long,$lat); }
  push @{$path},[$xs,$ys,$id];
 }
 $query->finish();

 $query=$dbh->prepare("SELECT k,v FROM current_way_tags WHERE id=?");
 $query->execute($wayid);
 my $attributes={};
 while (($k,$v)=$query->fetchrow_array()) { ${$attributes}{$k}=$v; }
 $query->finish();

 return ($path,$attributes);
}

# ----- Lat/long <-> coord conversion

sub lat2coord  { return  (lat2y($_[0])-$basey)*$masterscale; }
sub long2coord { return      ($_[0]-$baselong)*$masterscale; }
sub lat2y     { return 180/pi * log(Math::Trig::tan(pi/4+$_[0]*(pi/180)/2)); }

sub ll2osgb  { ($e,$n)=ll_to_grid($_[1],$_[0]);
      $n=($n-$basey)*$masterscale;
      $e=($e-$basex)*$masterscale;
      return ($e,$n); }

# ================================================================
# Output routines

# ----- Write Postscript header and footer

sub Output_Header {
 print OUTFILE <<EOF;
%!PS-Adobe-3.0
%%BoundingBox: 0 -10 $mmx $mmy
%%EndComments
%%BeginProlog

% draws rotated rectangles filled with lines
% Usage: x y w h s a LineFill
%  x,y = lower left corner of unrotated rectangle
%  w,h = width and height of unrotated rectangle
%    s = distance between the lines
%    a = rotation angle
/LineFill {gsave
 /a exch def /s exch def
 /h exch def /w exch def
 /y exch def /x exch def
 x w 2 div add y h 2 div add translate
 /PFs w dup mul h dup mul add sqrt def
 0 0 moveto a rotate PFs -2 div dup translate
 0 s PFs floor cvi {0 moveto 0 PFs rlineto} for
 stroke
 grestore} def

/linewidth {0.5 mul setlinewidth} def
/normwidth {5} def
/mywidth {normwidth} def
/addwidth {1} def
/m {newpath moveto} bind def
/l {lineto} bind def
/S {stroke} bind def
/F {fill} bind def
/dot { currentpoint newpath 1 0 360 arc fill } def
/color {} def
/DS {gsave mywidth addwidth add linewidth S grestore
 color mywidth linewidth S} def
/DSo {mywidth addwidth add linewidth S} def
/DSi {color mywidth linewidth S} def
/HS {1 setgray gsave mywidth addwidth add linewidth S grestore
 color mywidth linewidth S} def
/FS {gsave addwidth linewidth S grestore color F} def
/vshift -4.3 def
%/Cshow {currentpoint stroke m dup stringwidth pop -2 div vshift rmoveto show} def
/Cshow {gsave currentpoint translate rotate 0 0 m dup stringwidth pop -2 div vshift rmoveto show grestore} def
%
%/building    {204 255 div 150 255 div 150 255 div setrgbcolor} def
/building  {/mywidth{0.5} def /color{204 255 div 150 255 div 150 255 div setrgbcolor} def 1 setgray} def
/construction{156 255 div 156 255 div 108 255 div setrgbcolor} def
/university  {/mywidth{1} def /color{240 255 div 240 255 div 216 255 div setrgbcolor}def} def
/parking     {250 255 div 249 255 div 179 255 div setrgbcolor} def
/quarry {0.5 0.5 0 setrgbcolor} def
/sport       {51 255 div 203 255 div 153 255 div setrgbcolor} def
/pitch {0.4 0.8 0.2 setrgbcolor} def

/military  {clip 247 255 div 106 255 div 103 255 div setrgbcolor 0.2 setlinewidth} def
/mil {2 -45 LineFill grestore % undo the clip
} def

/forest      {140 255 div 199 255 div 107 255 div setrgbcolor} def
/meadow {0.5 0.9 0.5 setrgbcolor} def
/park        {205 255 div 246 255 div 202 255 div setrgbcolor} def
/allotments  {200 255 div 176 255 div 132 255 div setrgbcolor} def
/zoo         {173 255 div 248 255 div 172 255 div setrgbcolor} def
/villagegreen{207 255 div 235 255 div 171 255 div setrgbcolor} def
/wastewater {0.2 0.4 0.2 setrgbcolor} def
/cemetery    {173 255 div 203 255 div 173 255 div setrgbcolor} def
/farm        {239 255 div 219 255 div 189 255 div setrgbcolor} def
/residential {222 255 div setgray} def
/commercial {0.8 0.8 0.5 setrgbcolor} def
/industrial  {222 255 div 208 255 div 213 255 div setrgbcolor} def
/water {181 255 div 208 255 div 208 255 div setrgbcolor} def
%
/runway      {189 255 div 186 255 div 206 255 div setrgbcolor 4 setlinewidth} def
/taxiway     {189 255 div 186 255 div 206 255 div setrgbcolor} def
/apron       {239 255 div 211 255 div 1 setrgbcolor} def
%
/motorway    {/color {132 255 div 154 255 div 198 255 div setrgbcolor} def} def
/motorwaylink{motorway /mywidth {2} def} def
/trunk       {/color {169 255 div 218 255 div 169 255 div setrgbcolor} def /mywidth {4} def} def
/trunklink   {trunk /mywidth{1} def}def
/primary     {/mywidth {4} def /color {236 255 div 152 255 div 154 255 div setrgbcolor} def} def
/secondary   {/mywidth {3} def /color{253 255 div 214 255 div 164 255 div setrgbcolor} def} def
/tertiary    {/mywidth {2} def /color{250 255 div 249 255 div 179 255 div setrgbcolor} def} def
/resiary     {/mywidth {1} def /color{1 setgray} def} def
/service     {/mywidth {0.8} def /color{1 setgray} def} def
/living      {1 linewidth 0.6 setgray} def
/pedestrian  {/mywidth{1} def /color{0.93 setgray} def} def
/road {2 linewidth [2] 0 setdash 1 0 0 setrgbcolor} def
/rail        {0.4 setgray /mywidth {2} def /color{[3] 0 setdash 1 setgray} def} def
/raildisused {/mywidth {2} def /color{[2] 0 setdash 1 setgray} def} def
%
/track       {[0.5] 1 setdash 0.8 linewidth 154 255 div 105 255 div 5 255 div setrgbcolor} def
/track1      {[] 0 setdash 0.8 linewidth 154 255 div 105 255 div 5 255 div setrgbcolor} def
/track2      {[3 1] 0 setdash 0.8 linewidth 154 255 div 105 255 div 5 255 div setrgbcolor} def
/track3      {[2 1] 0 setdash 0.8 linewidth 154 255 div 105 255 div 5 255 div setrgbcolor} def
/track4      {[1.5 1 0.5 1.5] 0 setdash 0.8 linewidth 154 255 div 105 255 div 5 255 div setrgbcolor} def
/track5      {[1 1] 0 setdash 0.8 linewidth 154 255 div 105 255 div 5 255 div setrgbcolor} def
/path        {[1] 0 setdash 0.8 linewidth} def
/steps       {[0.5] 0 setdash 5 linewidth 249 255 div 130 255 div 116 255 div setrgbcolor} def
/footway     {[1] 0 setdash /mywidth {1} def /color{249 255 div 130 255 div 116 255 div setrgbcolor} def} def
/cycleway    {[1] 0 setdash 1 linewidth 0 0 254 255 div setrgbcolor} def
/bridleway   {[1] 0 setdash 1 linewidth 0 128 255 div 0 setrgbcolor} def
%
/nothing {} def
%
/reset {
 0 setgray
 [] 0 setdash
 /mywidth {normwidth} def
 mywidth linewidth
 /color {} def
} def
%
/reencodeISO {
dup dup findfont dup length dict begin
{ 1 index /FID ne { def }{ pop pop } ifelse } forall
currentdict /CharStrings known {
	CharStrings /Idieresis known {
		/Encoding ISOLatin1Encoding def } if
} if
currentdict end definefont
} def
/ISOLatin1Encoding [
/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef
/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef
/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef
/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef
/space/exclam/quotedbl/numbersign/dollar/percent/ampersand/quoteright
/parenleft/parenright/asterisk/plus/comma/minus/period/slash
/zero/one/two/three/four/five/six/seven/eight/nine/colon/semicolon
/less/equal/greater/question/at/A/B/C/D/E/F/G/H/I/J/K/L/M/N
/O/P/Q/R/S/T/U/V/W/X/Y/Z/bracketleft/backslash/bracketright
/asciicircum/underscore/quoteleft/a/b/c/d/e/f/g/h/i/j/k/l/m
/n/o/p/q/r/s/t/u/v/w/x/y/z/braceleft/bar/braceright/asciitilde
/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef
/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef
/.notdef/dotlessi/grave/acute/circumflex/tilde/macron/breve
/dotaccent/dieresis/.notdef/ring/cedilla/.notdef/hungarumlaut
/ogonek/caron/space/exclamdown/cent/sterling/currency/yen/brokenbar
/section/dieresis/copyright/ordfeminine/guillemotleft/logicalnot
/hyphen/registered/macron/degree/plusminus/twosuperior/threesuperior
/acute/mu/paragraph/periodcentered/cedilla/onesuperior/ordmasculine
/guillemotright/onequarter/onehalf/threequarters/questiondown
/Agrave/Aacute/Acircumflex/Atilde/Adieresis/Aring/AE/Ccedilla
/Egrave/Eacute/Ecircumflex/Edieresis/Igrave/Iacute/Icircumflex
/Idieresis/Eth/Ntilde/Ograve/Oacute/Ocircumflex/Otilde/Odieresis
/multiply/Oslash/Ugrave/Uacute/Ucircumflex/Udieresis/Yacute
/Thorn/germandbls/agrave/aacute/acircumflex/atilde/adieresis
/aring/ae/ccedilla/egrave/eacute/ecircumflex/edieresis/igrave
/iacute/icircumflex/idieresis/eth/ntilde/ograve/oacute/ocircumflex
/otilde/odieresis/divide/oslash/ugrave/uacute/ucircumflex/udieresis
/yacute/thorn/ydieresis
] def
%%EndProlog
%%BeginSetup
%%EndSetup
gsave
%/Helvetica findfont 4 scalefont setfont
/Helvetica reencodeISO def
(Helvetica) findfont 4 scalefont setfont
EOF
#/
}

sub Parse_Path {
 if(!$attributes{$way}){ return 0; }
 my($way,$info,$type,$stroke,$keepname,$isarea,$name,$something,$building,$layer);
 $way=$_[0];
 $name="";
 $keepname=0;
 $isarea=0;
 $layer=5;
 # Layers: 0=landuse  1=water,building  2=military
 #         3=highways 4=rail 5=single stroke
 if($attributes{$way}){
  $keystr="";
  $type="nothing";
  $stroke="S";
  $something=0;
  $building=0;
  foreach $k (keys %{$attributes{$way}}) {
  unless ($k eq 'created_by' or $k=~/^osmarender/) {
   $v=${$attributes{$way}}{$k};
   $kv="$k=$v";
   $keystr.="$k=".$v."; ";

   if($k=~/^name$/) { $name=$v; }
   if($k=~/^ref$/ && !$name) { $name=$v; }

   if(!$something && !($kv=~/^area=yes$/)){ $something=1; }

   # ignore unless it's something else:
   if($type=~/^nothing$/){
    if($kv=~/^addr:interpolation=/){ $type="ignore"; }
    if($kv=~/^aerialway=/){ $type="ignore"; }
    if($kv=~/^amenity=bus_station$/){ $type="ignore"; }
    if($kv=~/^amenity=vivarium$/){ $type="ignore"; }
    if($kv=~/^barrier=/){ $type="ignore"; }
    if($kv=~/^boundary=/){ $type="ignore"; }
    if($kv=~/^boundary=administrative$/){ $type="ignore"; }
    if($kv=~/^construction=/){ $type="ignore"; }
    if($kv=~/^fenced=/){ $type="ignore"; }
    if($kv=~/^future_landuse=/){ $type="ignore"; }
    if($kv=~/^highway=construction/){ $type="ignore"; }
    if($kv=~/^highway=crossing$/){ $type="ignore"; }
    if($kv=~/^highway=elevator$/){ $type="ignore"; }
    if($kv=~/^leisure=stadium$/){ $type="ignore"; }
    if($kv=~/^level=/){ $type="ignore"; }
    if($kv=~/^man_made=pipeline$/){ $type="ignore"; }
    if($kv=~/^military=abandoned$/){ $type="ignore"; }
    if($kv=~/^natural=cliff$/){ $type="ignore"; }
    if($kv=~/^place=suburb$/){ $type="ignore"; }
    if($kv=~/^power=line$/){ $type="ignore"; }
    if($kv=~/^power=minor_line$/){ $type="ignore"; }
    if($kv=~/^public_transport=platform$/){ $type="ignore"; }
    if($kv=~/^railway=abandoned$/){ $type="ignore"; }
    if($kv=~/^railway=narrow_gauge$/){ $type="ignore"; }
    if($kv=~/^railway=platform$/){ $type="ignore"; }
    if($kv=~/^railway=preserved$/){ $type="ignore"; }
    if($kv=~/^railway=tram$/){ $type="ignore"; }
    if($kv=~/^railway=turntable$/){ $type="ignore"; }
    if($kv=~/^route=ferry$/){ $type="ignore"; }
    if($kv=~/^source=/){ $type="ignore"; }
    if($kv=~/^TODO=/){ $type="ignore"; }
   }

   # areas:
   if($kv=~/^landuse=military$/){ $type="military"; $stroke="mil"; }
   if($kv=~/^military=/ && !($kv=~/^military=abandoned$/)){ $type="military"; $stroke="mil"; }
   if($kv=~/^landuse=construction$/){ $type="construction"; $stroke="F"; }
   if($kv=~/^landuse=brownfield$/){ $type="construction"; $stroke="F"; }
   if($kv=~/^landuse=greenfield$/){ $type="construction"; $stroke="F"; }
   if($kv=~/^landuse=forest$/){ $type="forest"; $stroke="F"; $keepname=1; }
   if($kv=~/^natural=wood$/){ $type="forest"; $stroke="F"; $keepname=1; }
   if($kv=~/^landuse=wood$/){ $type="forest"; $stroke="F"; $keepname=1; }
   if($kv=~/^landuse=farmland$/){ $type="farm"; $stroke="F"; }
   if($kv=~/^landuse=farm$/){ $type="farm"; $stroke="F"; }
   if($kv=~/^landuse=farmyard$/){ $type="farm"; $stroke="F"; }
   if($kv=~/^landuse=vineyard$/){ $type="farm"; $stroke="F"; }
   if($kv=~/^natural=land$/){ $type="farm"; $stroke="F"; }
   if($kv=~/^landuse=meadow$/){ $type="meadow"; $stroke="F"; }
   if($kv=~/^landuse=grass$/){ $type="meadow"; $stroke="F"; }
   if($kv=~/^natural=scrub$/){ $type="meadow"; $stroke="F"; }
   if($kv=~/^landuse=clearance$/){ $type="meadow"; $stroke="F"; }
   if($kv=~/^landuse=park$/){ $type="park"; $stroke="F"; }
   if($kv=~/^leisure=common$/){ $type="park"; $stroke="F"; }
   if($kv=~/^leisure=park$/){ $type="park"; $stroke="F"; }
   if($kv=~/^leisure=garden$/){ $type="park"; $stroke="F"; }
   if($kv=~/^man_made=wastewater_plant$/){ $type="wastewater"; $stroke="F"; }
   if($kv=~/^tourism=zoo$/){ $type="zoo"; $stroke="F"; }
   if($kv=~/^landuse=allotments$/){ $type="allotments"; $stroke="F"; }
   if($kv=~/^leisure=playground$/){ $type="park"; $stroke="F"; }
   if($kv=~/^landuse=recreation_ground$/){ $type="park"; $stroke="F"; }
   if($kv=~/^landuse=village_green$/){ $type="villagegreen"; $stroke="F"; }
   if($kv=~/^landuse=green$/){ $type="park"; $stroke="F"; }
   if($kv=~/^landuse=cemetery$/){ $type="cemetery"; $stroke="F"; }
   if($kv=~/^amenity=grave_yard$/){ $type="cemetery"; $stroke="F"; }
   if($kv=~/^landuse=residential$/){ $type="residential"; $stroke="F"; }
   if($kv=~/^place=town$/){ $type="residential"; $stroke="F"; }
   if($kv=~/^landuse=commercial$/){ $type="commercial"; $stroke="F"; }
   if($kv=~/^landuse=retail$/){ $type="commercial"; $stroke="F"; }
   if($kv=~/^landuse=(industrial|railway)$/){ $type="industrial"; $stroke="F"; }
   if($kv=~/^landuse=quarry$/){ $type="quarry"; $stroke="F"; }
   if($kv=~/^landuse=landfill$/){ $type="quarry"; $stroke="F"; }
   if($kv=~/^amenity=parking$/){ $type="parking"; $stroke="F"; }
   if($kv=~/^amenity=bicycle_parking$/){ $type="parking"; $stroke="F"; }
   if($kv=~/^leisure=pitch$/){ $type="pitch"; $stroke="F"; }
   if($kv=~/^sport=/){ $type="sport"; $stroke="F"; }
   if($kv=~/^leisure=sports_centre$/){ $type="sport"; $stroke="F"; }

   if($kv=~/^building=/){ $type="building"; $stroke="F"; $building=1; }
   if($kv=~/^historic=ruins$/){ $type="building"; $stroke="F"; }
   if($kv=~/^amenity=fire_station$/){ $type="building"; $stroke="F"; }
   if($kv=~/^amenity=place_of_worship$/){ $type="building"; $stroke="F"; }
   if($kv=~/^amenity=public_building$/){ $type="building"; $stroke="F"; }
   if($kv=~/^amenity=restaurant$/){ $type="building"; $stroke="F"; }
   if($kv=~/^amenity=townhall$/){ $type="building"; $stroke="F"; }
   if($kv=~/^amenity=university$/){ $type="university"; $stroke="FS0"; }
   if($kv=~/^amenity=kindergarten$/){ $type="building"; $stroke="F"; }
   if($kv=~/^amenity=school$/){ $type="building"; $stroke="F"; }
   if($kv=~/^amenity=hospital$/){ $type="building"; $stroke="F"; }
   if($kv=~/^tourism=hotel$/){ $type="building"; $stroke="F"; }
   if($kv=~/^shop=/){ $type="building"; $stroke="F"; }
   if($kv=~/^amenity=cafe$/){ $type="building"; $stroke="F"; }
   if($kv=~/^power=station$/){ $type="building"; $stroke="F"; }
   if($kv=~/^man_made=water_tower$/){ $type="building"; $stroke="F"; }
   if($kv=~/^aeroway=aerodrome$/){ $type="apron"; $stroke="F"; }
   if($kv=~/^aeroway=apron$/){ $type="apron"; $stroke="F"; }
   if($kv=~/^aeroway=terminal$/){ $type="building"; $stroke="F"; }

   if($kv=~/^natural=water$/){ $type="water"; $stroke="F"; $keepname=1; }
   if($kv=~/^leisure=water_park$/){ $type="water"; $stroke="F"; }
   if($kv=~/^landuse=basin$/){ $type="water"; $stroke="F"; }
   if($kv=~/^amenity=fountain$/){ $type="water"; $stroke="F"; }
   if($kv=~/^waterway=riverbank$/){ $type="water"; $stroke="F"; }
   if($kv=~/^waterway=riverbank$/){ $type="water"; $stroke="F"; }
   if($kv=~/^landuse=reservoir$/){ $type="water"; $stroke="F"; }
   if($kv=~/^area=yes$/){ $isarea=1; }

   # lines with borders:
   if($kv=~/^highway=motorway$/){ $type="motorway"; $stroke="DS"; $keepname=1; }
   if($kv=~/^highway=motorway_link$/){ $type="motorwaylink"; $stroke="DS"; }
   if($kv=~/^highway=trunk$/){ $type="trunk"; $stroke="DS"; $keepname=1; }
   if($kv=~/^highway=trunk_link$/){ $type="trunklink"; $stroke="DS"; }
   if($kv=~/^highway=primary/){ $type="primary"; $stroke="DS"; $keepname=1; }
   if($kv=~/^highway=secondary/){ $type="secondary"; $stroke="DS"; $keepname=1; }
   if($kv=~/^highway=tertiary/){ $type="tertiary"; $stroke="DS"; $keepname=1; }
   if($kv=~/^highway=residential$/){ $type="resiary"; $stroke="DS"; $keepname=1; }
   if($kv=~/^highway=unclassified$/){ $type="resiary"; $stroke="DS"; }
   if($kv=~/^highway=service$/){ $type="service"; $stroke="DS"; }
   if($kv=~/^highway=private$/){ $type="service"; $stroke="DS"; }
   if($kv=~/^railway=rail$/){ $type="rail"; $stroke="DS"; }
   if($kv=~/^railway=disused$/){ $type="raildisused"; $stroke="DS"; }
   if($kv=~/^highway=pedestrian$/){ $type="pedestrian"; $stroke="DS"; }

   if($type=~/^(nothing|ignore)$/){
    if($kv=~/^junction=roundabout$/){ $type="resiary"; $stroke="DS"; }
    if($kv=~/^highway=turning_circle$/){ $type="resiary"; $stroke="DS"; }
   }

   # simple lines:
   if($kv=~/^highway=track$/ && !($type=~/^track/)){ $type="track"; }
   if($kv=~/^tracktype=grade([1-5])$/){ $type="track$1"; }
   if($kv=~/^highway=unsurfaced$/){ $type="track"; }
   if($kv=~/^highway=ford$/){ $type="track"; }
   if($kv=~/^highway=footway$/){ $type="footway"; $stroke="HS"; }
   if($kv=~/^highway=cycleway$/){ $type="cycleway"; }
   if($kv=~/^highway=bridleway$/){ $type="bridleway"; }
   if($type=~/^(nothing|ignore)$/){
    if($kv=~/^cycleway=track$/){ $type="cycleway"; }
    if($kv=~/^bicycle=yes$/){ $type="cycleway"; }
   }
   if($kv=~/^highway=path$/){ $type="path"; }
   if($kv=~/^highway=steps$/){ $type="steps"; }
   if($kv=~/^highway=living_street$/){ $type="living"; }
   if($kv=~/^highway=road$/){ $type="road"; }
   if($kv=~/^highway=unknown$/){ $type="road"; }
   if($kv=~/^waterway=river$/){ $type="water"; $keepname=1; }
   if($kv=~/^waterway=canal$/){ $type="water 2 linewidth"; }
   if($kv=~/^waterway=stream$/){ $type="water 1 linewidth"; }
   if($kv=~/^waterway=drain$/){ $type="water 1 linewidth"; }
   if($kv=~/^aeroway=taxiway$/){ $type="taxiway"; }
   if($kv=~/^aeroway=runway$/){ $type="runway"; }

  }
  }
  if($isarea){
   if($stroke eq "DS"){
    $stroke="FS";
   }else{
    $stroke="F";
   }
   if($type eq "footway"){ $type="pedestrian"; $stroke="FS"; }
  }
  # default layer: 5
  if($stroke eq "F"){ $layer=0; }
  if($building){ $type="building"; }
  if($type eq "building"){ $stroke="FS"; }
  if($stroke eq "FS"){ $layer=1; }
  if($stroke eq "FS0"){ $layer=0; $stroke="FS"; }
  if($type eq "water"){ $layer=1; }
  if($type eq "military"){ $layer=2; }
  if($stroke eq "DS"){ $layer=3; }
  if($type =~ /^rail/ && $stroke eq "DS"){ $layer=4; }
  if($type=~/^ignore$/ || !$something){
   $addinfo{$way}=0;
   return 0;
  }
  $keystr =~ s/; $//;
  $keystr = substr($keystr,0,240);
  $keystr =~ s/–/-/g;
  $keystr =~ s/[„“]/"/g;
  $name =~ s/[„“]/"/g;
  $name =~ s/–/-/g;
  $name =~ s/ä/ae/g;
  $name =~ s/ö/oe/g;
  $name =~ s/ü/ue/g;
  $name =~ s/Ä/Ae/g;
  $name =~ s/Ö/Oe/g;
  $name =~ s/Ü/Ue/g;
  $name =~ s/ß/ss/g;
  $name =~ s/[éè]/e/g;
  $name =~ s/&amp;/\&/g;
  $info="$way $keystr";
 }
 if(!$keepname){ $name=""; }
 $addinfo{$way}=[$type,$stroke,$info,$layer,$name];
 return 0;
}

sub Output_New_Layer {
 my $layername=$_[0];
 my $colour=$_[1];
 print OUTFILE <<EOF;
%Layer: $layername
EOF
}

sub Output_Footer {
 print OUTFILE <<EOF;
%%PageTrailer
grestore showpage
%%Trailer
%%Pages: 1
%%EOF
EOF
}



# ================================================================
# OSM quadtile routines
# based on original Ruby code by Tom Hughes

sub tile_for_point {
 my $lat=$_[0]; my $lon=$_[1];
 return tile_for_xy(round(($lon+180)*65535/360),round(($lat+90)*65535/180));
}

sub round {
 return int($_[0] + .5 * ($_[0] <=> 0));
}

sub tiles_for_area {
 my $minlat=$_[0]; my $minlon=$_[1];
 my $maxlat=$_[2]; my $maxlon=$_[3];

 $minx=round(($minlon + 180) * 65535 / 360);
 $maxx=round(($maxlon + 180) * 65535 / 360);
 $miny=round(($minlat + 90 ) * 65535 / 180);
 $maxy=round(($maxlat + 90 ) * 65535 / 180);
 @tiles=();

 for ($x=$minx; $x<=$maxx; $x++) {
  for ($y=$miny; $y<=$maxy; $y++) {
   push(@tiles,tile_for_xy($x,$y));
  }
 }
 return @tiles;
}

sub tile_for_xy {
 my $x=$_[0];
 my $y=$_[1];
 my $t=0;
 my $i;

 for ($i=0; $i<16; $i++) {
  $t=$t<<1;
  unless (($x & 0x8000)==0) { $t=$t | 1; }
  $x<<=1;

  $t=$t<< 1;
  unless (($y & 0x8000)==0) { $t=$t | 1; }
  $y<<=1;
 }
 return $t;
}

sub sql_for_area {
 my $minlat=$_[0]; my $minlon=$_[1];
 my $maxlat=$_[2]; my $maxlon=$_[3];
 my $prefix=$_[4];
 my @tiles=tiles_for_area($minlat,$minlon,$maxlat,$maxlon);

 my @singles=();
 my $sql='';
 my $tile;
 my $last=-2;
 my @run=();
 my $rl;

 foreach $tile (sort @tiles) {
  if ($tile==$last+1) {
   # part of a run, so keep going
   push (@run,$tile);
  } else {
   # end of a run
   $rl=@run;
   if ($rl<3) { push (@singles,@run); }
      else { $sql.="${prefix}tile BETWEEN ".$run[0].' AND '.$run[$rl-1]." OR "; }
   @run=();
   push (@run,$tile);
  }
  $last=$tile;
 }
 $rl=@run;
 if ($rl<3) { push (@singles,@run); }
    else { $sql.="${prefix}tile BETWEEN ".$run[0].' AND '.$run[$rl-1]." OR "; }
 if ($#singles>-1) { $sql.="${prefix}tile IN (".join(',',@singles).') '; }
 $sql=~s/ OR $//;
 return $sql;
}

__END__

=head1 NAME

B<osm2ai.pl>

=head1 DESCRIPTION

osm2ai takes OpenStreetMap data and converts it to Postscript.

You can either take the data from a .osm XML file (via the
site's Export tab), or for bigger exports, by specifying a
bounding box to your own OpenStreetMap-like MySQL
database.

The data is wholly unstyled - the idea is that you make the
cartographic decisions yourself. Data is grouped into layers
to help you.

=head1 SYNOPSIS

osm2ai.pl --input map.osm --projection osgb --output mymap.ai

osm2ai.pl --xmin -2.3 --xmax -2.2 --ymin 52.15 --ymax 52.25
          --projection osgb --output mymap.ai

=head1 OPTIONS

=over 2

=item B<--projection> name

The projection for your map. Should be either B<osgb>
(Ordnance Survey National Grid) or B<mercator> (spherical
Mercator).

=item B<--input> filename

Specifies the input OSM XML file, if you're reading from
file.

=item B<--output> filename

Specifies the output filename. Defaults to output.ai.

=item B<--xmin> longitude
B<--xmax> longitude
B<--ymin> latitude
B<--ymax> latitude

The bounding box of the area you want to extract, if you're
reading from a database.

=item B<--db> database_name
B<--user> database_user
B<--password> database_password

Connection details for the MySQL database which contains the
data, if you're reading from a database. If you don't supply
this, the DBNAME, DBUSER and DBPASS environment variables
will be used. If they're not set, it'll default to
openstreetmap, openstreetmap and openstreetmap.

=item B<--man>

Output the full documentation.

=head1 FILTERS

Rather than just bunching everything into one layer, this
script can filter by tag. So you could put primary roads in
one layer, secondary in another, and ignore canals
completely.

Create a plain text file, and add lines like this:

B<motorway: highway=motorway>

Means "put ways tagged with highway=motorway in a 'motorway'
layer".

B<railway: railway=*>

Means "put ways with any railway tag whatsoever in a 'railway'
layer.

B<other: =>

Means "put anything else in an 'other' layer".

The tests are carried out in the order you give them. A way
will only ever be put into one layer, even if it fulfils
two conditions.

=head1 SETTING UP A DATABASE

If you want to make a map of a greater area than is available
through the site's Export tab, you will need to set up a MySQL
database and populate it with OpenStreetMap data. This will
typically involve downloading B<planet.osm> and then uploading
it using a program such as B<planetosm-to-db.pl>.

For details, see http://wiki.openstreetmap.org/index.php/Planet.osm

=head1 OUTPUT

The resulting file is simple Postscript. Each way is stored as
a path, and some attributes are translated to line styles.

The ways aren't cropped to the bounding box, but the papersize
is set to the squared bounding box.

=head1 PREREQUISITES

This script needs four modules which you almost certainly have
(DBI, Math::Trig, Pod::Usage, Getopt::Long) and one which you
might not (Geo::Coordinates::OSGB).

=head1 LIMITATIONS

It doesn't do POIs or relations, only tagged ways.

The quadtile stuff really ought to be in a library.

The whole caboodle should be on the OSM Export tab.

There should be a grid, or constant scale, or something, so you can mix and
match different maps.

Reading from an .osm file has been kludged on really messily.

=head1 COPYRIGHT

Written by Richard Fairhurst, 2007-2008.

Quadtile code adapted from Tom Hughes' Ruby OSM server code -
thanks Tom!

This program really is free software. It's distributed under
the terms of the WTFPL. You may do what the fuck you want to
with it. See http://sam.zoy.org/wtfpl/COPYING for details.

If you use it to extract data from OpenStreetMap which isn't
yours, the output must of course only be published under
the terms of whatever licence applies to the data.

=cut
