#!/usr/bin/python -u
# -*- coding: utf-8 -*-

import cairo
import cgi
import mapnik
import os
import shutil
import sys
import tempfile
import resource

# Limit maximum CPU time
# The Postscript output format can sometimes take hours
resource.setrlimit(resource.RLIMIT_CPU,(90,90))

# Limit memory usage
# Some odd requests can cause extreme memory usage
resource.setrlimit(resource.RLIMIT_AS,(4000000000, 4000000000))

# Routine to output HTTP headers
def output_headers(content_type, filename = "", length = 0):
  print "Content-Type: %s" % content_type
  if filename:
    print "Content-Disposition: attachment; filename=\"%s\"" % filename
  if length:
    print "Content-Length: %d" % length
  print ""

# Routine to output the contents of a file
def output_file(file):
  file.seek(0)
  shutil.copyfileobj(file, sys.stdout)

# Routine to get the size of a file
def file_size(file):
  return os.fstat(file.fileno()).st_size

# Routine to report an error
def output_error(message):
  output_headers("text/html")
  print "<html>"
  print "<head>"
  print "<title>Error</title>"
  print "</head>"
  print "<body>"
  print "<h1>Error</h1>"
  print "<p>%s</p>" % message
  print "</body>"
  print "</html>"

# Parse CGI parameters
form = cgi.FieldStorage()

if not os.environ.has_key('HTTP_USER_AGENT'):
  os.environ['HTTP_USER_AGENT'] = 'NONE'

# Abort if the load average on the machine is too high
loadavg = float(open("/proc/loadavg").readline().split(" ")[0])
if loadavg > 27.0:
  print "Status: 503 Service Unavailable"
  output_error("The load average on the server is too high at the moment. Please wait a few minutes before trying again.")

# Block some user-agents scraping requests
elif os.environ['HTTP_USER_AGENT'] == 'Lynx/2.8.6rel.5 libwww-FM/2.14 SSL-MM/1.4.1 OpenSSL/0.9.8k':
  print "Status: 503 Service Unavailable"
  output_error("The load average on the server is too high at the moment. Please wait a few minutes before trying again.")
elif os.environ['HTTP_USER_AGENT'] == 'Wget/1.11.4':
  print "Status: 503 Service Unavailable"
  output_error("The load average on the server is too high at the moment. Please wait a few minutes before trying again.")
elif os.environ['HTTP_USER_AGENT'] == 'dummy':
  print "Status: 503 Service Unavailable"
  output_error("The load average on the server is too high at the moment. Please wait a few minutes before trying again.")
 
# Validate the parameters
elif not form.has_key("bbox"):
  # No bounding box specified
  output_error("No bounding box specified")
elif not form.has_key("scale"):
  # No scale specified
  output_error("No scale specified")
elif not form.has_key("format"):
  # No format specified
  output_error("No format specified")
else:
  # Create projection object
  prj = mapnik.Projection("+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs +over");

  # Get the bounds of the area to render
  bbox = [float(x) for x in form.getvalue("bbox").split(",")]

  if bbox[0] >= bbox[2] or bbox[1] >= bbox[3]:
    # Bogus bounding box
    output_error("Invalid bounding box")
  else:
    # Project the bounds to the map projection
    bbox = mapnik.forward_(mapnik.Envelope(*bbox), prj)

    # Calculate the size of the final rendered image
    scale = float(form.getvalue("scale"))
    # Reject some values from scripts
    #if scale == 17900 or scale == 5004:
    if scale == 220000 or scale == 20000 or scale == 5000:
      output_error("Automated requests to the /export API are not permitted. Please see http://wiki.openstreetmap.org/wiki/Tile_usage_policy or contact the OSM 'dev' mailing list for further details.")
    else:
      width = int(bbox.width() / scale / 0.00028)
      height = int(bbox.height() / scale / 0.00028)
      
      # Limit the size of map we are prepared to produce
      if width * height > 4000000:
        # Map is too large (limit is approximately A2 size)
        output_error("Map too large")
      else:
        # Create map
        map = mapnik.Map(width, height)
        
        # Load map configuration
        mapnik.load_map(map, "/home/jburgess/live/osm.xml")
        
        # Zoom the map to the bounding box
        map.zoom_to_box(bbox)
        
        # Render the map
        if form.getvalue("format") == "png":
          image = mapnik.Image(map.width, map.height)
          mapnik.render(map, image)
          png = image.tostring("png") 
          output_headers("image/png", "map.png", len(png))
          sys.stdout.write(png)
        elif form.getvalue("format") == "jpeg":
          image = mapnik.Image(map.width, map.height)
          mapnik.render(map, image)
          jpeg = image.tostring("jpeg") 
          output_headers("image/jpeg", "map.jpg", len(jpeg))
          sys.stdout.write(jpeg)
        elif form.getvalue("format") == "svg":
          file = tempfile.NamedTemporaryFile()
          surface = cairo.SVGSurface(file.name, map.width, map.height)
          mapnik.render(map, surface)
          surface.finish()
          output_headers("image/svg+xml", "map.svg", file_size(file))
          output_file(file)
        elif form.getvalue("format") == "pdf":
          file = tempfile.NamedTemporaryFile()
          surface = cairo.PDFSurface(file.name, map.width, map.height)
          mapnik.render(map, surface)
          surface.finish()
          output_headers("application/pdf", "map.pdf", file_size(file))
          output_file(file)
        elif form.getvalue("format") == "ps":
          file = tempfile.NamedTemporaryFile()
          surface = cairo.PSSurface(file.name, map.width, map.height)
          mapnik.render(map, surface)
          surface.finish()
          output_headers("application/postscript", "map.ps", file_size(file))
          output_file(file)
        else:
          output_error("Unknown format '%s'" % form.getvalue("format"))
          
