#!/usr/bin/perl -w

# Our database
$dbname = 'osm';

# Default verbosity level? Can be increased with -v, decreased with -q
$verblev = 0;

# The area which we cover
$x1 = 11.01;
$y1 = 49.56;
$x2 = 11.04;
$y2 = 49.58;

# There should be no need to touch anything below this line.
# ---------------------------------------------------------------------------

use DBI;
use POSIX qw(strftime mktime);
use Pg::hstore;
use JSON;

# Par. 0: Level on which this gets printed
# Par. 1: Text
sub printlev($$) {
  unless (defined($printlev_atbeginofline)) { $printlev_atbeginofline = 1; }
  if ($verblev >= $_[0]) {
    if ($printlev_atbeginofline) {
      print(strftime("[%Y%m%d-%H%M%S] ", localtime(time())));
    }
    print($_[1]);
    if ($_[1] =~ m/\n$/) {
      $printlev_atbeginofline = 1;
    } else {
      $printlev_atbeginofline = 0;
    }
  }
}

# Par. 0: default value to return if nothing is defined
# Par. 1: the hashref containing our variables.
# Par. 2: variables to query. The LAST one that exists will be returned.
sub fetchlastofhr($$@) {
  my $res; my $hr; my @vns;
  ($res, $hr, @vns) = @_;
  for (my $i = 0; $i < @vns; $i++) {
    my $vn = $vns[$i];
    if (defined($hr->{$vn})) {
      $res = $hr->{$vn};
    }
  }
  return $res;
}

# Helper functions for the CGI variant. Taken from the openstreetmap-Wiki,
# which in turn is almost a direct copy of a GPL'd script included in (amongst
# others) the mapnik sourcecode. -> GPL code
use Math::Trig;
sub Project($$$) {
  my ($X, $Y, $Zoom) = @_;
  my $Unit = 1 / (2 ** $Zoom);
  my $relY1 = $Y * $Unit;
  my $relY2 = $relY1 + $Unit;
  my $LimitY = pi;
  my $RangeY = 2 * $LimitY;
  $relY1 = $LimitY - $RangeY * $relY1;
  $relY2 = $LimitY - $RangeY * $relY2;
  my $Lat1 = ProjectMercToLat($relY1);
  my $Lat2 = ProjectMercToLat($relY2);
  $Unit = 360 / (2 ** $Zoom);
  my $Long1 = -180 + $X * $Unit;
  return ($Lat2, $Long1, $Lat1, $Long1 + $Unit); # S,W,N,E
}
sub ProjectMercToLat($) {
  return rad2deg(atan(sinh($_[0])));
}
sub ProjectF($) {
  $Lat = deg2rad($_[0]);
  return (log(tan($Lat) + sec($Lat)));
}
# ----- end of copied helper functions -----

# print that drops whitespace when not in verbose mode.
sub printwows($) {
  my $res = $_[0];
  if ($verblev > 0) {
    print($res); return;
  }
  $res =~ s/^\s+//g;
  $res =~ s/"\s*:\s*/":/g;
  $res =~ s/[\r\n]//g;
  print($res);
}

# ----------------------------------------------------------------------------
# main()
# ----------------------------------------------------------------------------

$iscgi = 0;

# Parse commandline
foreach $a (@ARGV) {
  if ($a eq '-q') {
    $verblev--;
  } elsif ($a eq '-v') {
    $verblev++;
  } elsif ($a eq '--cgi') {
    $iscgi = 1;
  } else {
    print("Unknown parameter: $a\n");
    print("Syntax: $0 [-q] [-v]\n");
    print(" -q decreases verbosity, -v increases verbosity.\n");
    exit(1);
  }
}

if (defined($ENV{'REQUEST_URI'})) {
  $iscgi = 1;
  print("Content-type: application/json; charset=utf-8\n");
  print("Access-Control-Allow-Origin: *\n");
  print("Cache-Control: public, max-age=3600\n");
  print("\n");
  unless ($ENV{'REQUEST_URI'} =~ m!(\d+)/(\d+)/(\d+)\.json$!) {
    print("{\n\"_comment\": \"Sorry, query parameters not understood.\"\n}\n");
    exit(0);
  }
  my $cgiz = int($1); my $cgix = int($2); my $cgiy = int($3);
  #print("DEBUG: z=$cgiz x=$cgix y=$cgiy\n");
  if ($cgiz < 15) {
    print("{\n\"_comment\": \"No data will be returned at this zoom level.\"\n}\n");
  }
  ($y1, $x1, $y2, $x2) = Project($cgix, $cgiy, $cgiz);
  #print("DEBUG: mapped to ($x1 $y1) ($x2 $y2)\n");
}

unless ($dbh = DBI->connect("dbi:Pg:dbname=$dbname","","")) {
  print(STDERR "Failed to open database. Please try again later.\n"); exit(1);
}

$tx1 = $dbh->selectrow_array("select ST_X(ST_transform(ST_GeomFromText('POINT($x1 $y1)', 4326), 900913))");
$ty1 = $dbh->selectrow_array("select ST_Y(ST_transform(ST_GeomFromText('POINT($x1 $y1)', 4326), 900913))");
$tx2 = $dbh->selectrow_array("select ST_X(ST_transform(ST_GeomFromText('POINT($x2 $y2)', 4326), 900913))");
$ty2 = $dbh->selectrow_array("select ST_Y(ST_transform(ST_GeomFromText('POINT($x2 $y2)', 4326), 900913))");
# Why do crappy perl libraries always insist on using an "Object Oriented" interface
# for things where this does not make any sense at all?
my $crappyjsonoo = JSON->new()->latin1(); # Note: has to be latin1 instead of utf8
# for now, because the strings we get from the DB are WTF8, but are not marked
# as such. Therefore, we must not tell the JSON module to do utf8(), because
# otherwise it would encode the WTF8 twice.
# This will change with DBI::Pg modules >3 where UTF8 is properly handled.
if ($verblev > 0) {
  $crappyjsonoo = $crappyjsonoo->pretty();
}
my $cntr = 0;
if ($iscgi == 0) {
  print("// Note: this file has been autogenerated by $0\n");
  printwows("var FAUGeoJSON = {\n");
} else {
  printwows("{\n");
}
printwows("  \"type\": \"FeatureCollection\",\n");
printwows("  \"features\": [\n");
foreach $xtable ('planet_osm_polygon1', 'planet_osm_polygon2', 'planet_osm_line') { # 'planet_osm_polygon', 'planet_osm_line'
  my $querypart1 = " ((tags->'building:part') is not null)";
  my $table = 'planet_osm_polygon'; 
  if ($xtable eq 'planet_osm_polygon1') {
    $querypart1 = " (building is not null)";
  } elsif ($xtable eq 'planet_osm_line') {
    $table = $xtable;
  }
  # Note: && in this case is the postgis operator for 'bounding box overlaps'.
  my $sth = $dbh->prepare("select osm_id, way, building, 'tower:type', name, tags"
                        . " from $table where $querypart1"
                        . "   and (way && ST_MakeEnvelope($tx1, $ty1, $tx2, $ty2))");
  unless ($sth->execute()) {
    print(STDERR "Sorry, database query blew up.!\n");
    exit(1);
  }
  my $sth2 = $dbh->prepare("select ST_X(points) as x, ST_Y(points) as y"
                         . " from (select ST_astext((ST_dumppoints(ST_transform(geometry(?), 4326))).geom) as points) as points");
  my $sth4 = $dbh->prepare("select (ST_dumprings(?)).geom as rings");
  while ($row = $sth->fetchrow_hashref()) {
    if ($xtable eq 'planet_osm_polygon1') {
      my $hassubparts = $dbh->selectrow_array("select count(*)"
                                            . " from planet_osm_line"
                                            . " where ((tags->'building:part') is not null)"
                                            . " and (ST_Covers(" . $dbh->quote($row->{'way'}) . ", way) = true)");
      # The subparts usually cover the whole building, so we do NOT draw the
      # whole building but only the subparts.
      if ($hassubparts > 0) { next; }
    }
    my $hstoredec;
    if (defined($row->{'tags'})) {
      $hstoredec = Pg::hstore::decode($row->{'tags'});
    }
    my $levels = fetchlastofhr(1, $hstoredec, 'levels', 'building:levels');
    my $height = fetchlastofhr(undef, $hstoredec, 'height', 'building:height');
    unless ((defined($height)) && ($height =~ m/^[0-9.]+$/)) {
      undef($height);
    }
    my $minlevel = fetchlastofhr(0, $hstoredec,
                                 'min_levels', 'building:min_levels', 'min_level', 'building:min_level');
    my $minheight = fetchlastofhr(undef, $hstoredec, 'min_height', 'building:min_height');
    unless ((defined($minheight)) && ($minheight =~ m/^[0-9.]+$/)) {
      undef($minheight);
    }
    my $shape = fetchlastofhr(undef, $hstoredec, 'building:shape');
    my $material = fetchlastofhr(undef, $hstoredec,
                                 'building:material', 'building:facade:material', 'building:cladding');
    my $wallcolor = fetchlastofhr(undef, $hstoredec, 'building:color', 'building:colour');
    my $roofcolor = fetchlastofhr(undef, $hstoredec,
                                  'roof:color', 'roof:colour', 'building:roof:color', 'building:roof:colour');
    my $roofshape = fetchlastofhr(undef, $hstoredec, 'roof:shape', 'building:roof:shape');
    my $roofheight = fetchlastofhr(undef, $hstoredec, 'roof:height', 'building:roof:height');
    unless ((defined($roofheight)) && ($roofheight =~ m/^[0-9.]+$/)) {
      undef($roofheight);
    }
    my $roofmaterial = fetchlastofhr(undef, $hstoredec, 'roof:material', 'building:roof:material');
    if ($cntr != 0) {
      printwows(",\n");
    }
    $cntr++;
    my $jso = { "type" => "Feature" };
    $jso->{'id'} = (defined($row->{'osm_id'}) ? $row->{'osm_id'} : $cntr);
    if (defined($row->{'name'})) {
      $jso->{'name'} = $row->{'name'};
    }
    my @waystodecode = ();
    {
      local $sth4->{'PrintError'};  # We're fully aware that the execute can
      local $sth4->{'PrintWarn'};   # fail, no need to spam about it.
      if ($sth4->execute($row->{'way'})) {
        while ((my $nxtway) = $sth4->fetchrow_array()) {
          push(@waystodecode, $nxtway);
        }
      } else {
        push(@waystodecode, $row->{'way'});
      }
    }
    my @geomarr2 = ();
    foreach my $nxtway (@waystodecode) {
      unless ($sth2->execute($nxtway)) {
        print(STDERR "Sorry, decoding the way data exploded.\n");
        exit(1);
      }
      my ($onex, $oney);
      my @geomarr1 = ();
      while (($onex, $oney) = $sth2->fetchrow_array()) {
        my @onecoord = ( sprintf("%.5f", $onex), sprintf("%.5f", $oney));
        push (@geomarr1, \@onecoord);
      }
      push(@geomarr2, \@geomarr1);
      $sth2->finish();
    }
    my $geomhsh = { "type" => "Polygon",
                    "coordinates" => \@geomarr2 };
    $jso->{'geometry'} = $geomhsh;
    my $prophsh = { };
    if (defined($height)) {
      $prophsh->{'height'} = $height;
    }
    if (defined($minheight)) {
      $prophsh->{'minHeight'} = $minheight;
    }
    if (defined($levels)) {
      $prophsh->{'levels'} = $levels;
    }
    if (defined($minlevel)) {
      $prophsh->{'minLevel'} = $minlevel;
    }
    if (defined($shape)) {
      $prophsh->{'shape'} = $shape;
    }
    if (defined($material)) {
      $prophsh->{'material'} = $material;
    }
    if (defined($wallcolor)) {
      $prophsh->{'wallColor'} = $wallcolor;
    }
    if (defined($roofcolor)) {
      $prophsh->{'roofColor'} = $roofcolor;
    }
    if (defined($roofshape)) {
      if ($roofshape eq 'pyramidal') { $roofshape = 'pyramid'; }
      $prophsh->{'roofShape'} = $roofshape;
    }
    if (defined($roofmaterial)) {
      $prophsh->{'roofMaterial'} = $roofmaterial;
    }
    if (defined($roofheight)) {
      $prophsh->{'roofHeight'} = $roofheight;
    }
    $jso->{'properties'} = $prophsh;
    # Now dump it all.
    print($crappyjsonoo->encode($jso));
  }
  $sth4->finish();
  undef($sth4);
  $sth->finish();
  undef($sth);
}
printwows("\n  ]\n");
if ($iscgi == 0) {
  print("};\n");
} else {
  print("}\n");
}
