#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Generates a single large PNG image for a UK bounding box
# Tweak the lat/lon bounding box (ll) and image dimensions
# to get an image of arbitrary size.
#
# To use this script you must first have installed mapnik
# and imported a planet file into a Postgres DB using
# osm2pgsql.
#
# Note that mapnik renders data differently depending on
# the size of image. More detail appears as the image size
# increases but note that the text is rendered at a constant
# pixel size so will appear smaller on a large image.

from math import pi,cos,sin,log,exp,atan
from mapnik import *
import sys, os

if __name__ == "__main__":
    # Use Mapnik template from MAPNIK_MAP_FILE environment variable or osm.xml
    # file.
    try:
        map_file = os.environ['MAPNIK_MAP_FILE']
    except KeyError:
        map_file = "osm.xml"

    # Configuration options
    poster_filename = "poster.png" # Name of output PNG file.
    lonlat_bottom_left = Coord(12.10, 48.55) # Bottom-left corner of area of interest in Lon/Lat coordinates.
    lonlat_top_right = Coord(18.86, 51.06) # Top-right corner of area of interest in Lon/Lat coordinates.
    x_size = 14040 # A0 width at 300 DPI
    y_size = 9930 # A0 height at 300 DPI
    #x_size = 28080 # A0 width at 600 DPI
    #y_size = 19860 # A0 height at 600 DPI
    x_parts = 8 # Number of tiles in x-direction.
    y_parts = 8 # Number of tile in y-direction
    overlap = 32 # Number of pixels of tile's overlapping.
    borders = (400, 600, 400, 800) # Left, bottom, right and top white border of the poster.
    frame_line_width = 10 # Line width of frame around map.
    title = "OpenStreetMap - CTC Hiking Tracks Network - 30 September 2009" # Text of a title on the top.
    title_font_size = 300 # Font size of the title.
    title_offset = 700 # Offset of title bottom from the top of the poster.
    attribution = "\302\251 OpenStreetMap & contributors (http://www.openstreetmap.org/), CC-BY-SA" # Text of an attribtuion on the bottom.
    attribution_font_size = 100 # Font size of the attribution,
    attribution_offset = 400 # Offset of attribution bottom from the bottom of the poster.

    # Compute image size for map.
    x_size = x_size - borders[0] - borders[2]
    y_size = y_size - borders[1] - borders[3]

    # Compute rendered area size in target projection.
    projection = 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")
    target_bottom_left = projection.forward(lonlat_bottom_left)
    target_top_right = projection.forward(lonlat_top_right)

    # Modify area to respect aspect ratio of image.
    image_aspect = x_size / float(y_size)
    target_center = Coord((target_bottom_left.x + target_top_right.x)  * 0.5,
      (target_bottom_left.y + target_top_right.y) * 0.5)
    target_width = target_top_right.x - target_bottom_left.x
    target_height = target_top_right.y - target_bottom_left.y
    target_aspect = target_width / float(target_height)
    if image_aspect > target_aspect:
        target_bottom_left.x = target_center.x - (target_height * image_aspect * 0.5)
        target_top_right.x = target_center.x + (target_height * image_aspect * 0.5)
    else:
        target_bottom_left.y = target_center.y - (target_width / image_aspect * 0.5)
        target_top_right.y = target_center.y + (target_width / image_aspect * 0.5)

    # Update target dimensions.
    target_width = target_top_right.x - target_bottom_left.x
    target_height = target_top_right.y - target_bottom_left.y
    target_aspect = target_width / float(target_height)

    # Inform about visible area and load map data.
    lonlat_bottom_left = projection.inverse(target_bottom_left)
    lonlat_top_right = projection.inverse(target_top_right)
    print "Loading map of area (%f lon %f lat) - (%f lon %f lat)..." % (
      lonlat_bottom_left.x, lonlat_bottom_left.y, lonlat_top_right.x,
      lonlat_top_right.y),; sys.stdout.flush()
    part_x_size = x_size / x_parts
    part_y_size = y_size / y_parts
    m = Map(part_x_size + 2 * overlap, part_y_size + 2 * overlap)
    load_map(m, map_file)
    print "done."

    # Render tiles.
    start_x = target_bottom_left.x
    step_x = target_width / x_parts
    start_y = target_bottom_left.y
    step_y = target_height / y_parts
    target_overlap = overlap * (target_height / y_size)
    for x in range(x_parts):
        for y in range(y_parts):
            c0 = Coord(start_x + (x * step_x) - target_overlap, start_y + (y *
              step_y) - target_overlap)
            c1 = Coord(start_x + ((x + 1) * step_x) + target_overlap, start_y +
              ((y + 1) * step_y) + target_overlap)

            bbox = Envelope(c0.x, c0.y, c1.x, c1.y)
            m.zoom_to_box(bbox)

            # Render part to image.
            print "Rendering part (%d, %d)..." % (x, y),; sys.stdout.flush()
            image = Image(part_x_size + 2 * overlap, part_y_size + 2 * overlap)
            render(m, image)
            view = image.view(overlap, overlap, part_x_size, part_y_size)
            view.save('part%.2d%.2d.png' % (x, y), 'png')
            print "done."

    from cairo import ImageSurface, Context, FORMAT_RGB24, FONT_SLANT_NORMAL, \
      FONT_WEIGHT_BOLD

    # Join them to single image.
    print "Merging parts...",; sys.stdout.flush()
    x_size = part_x_size * x_parts + borders[0] + borders[2]
    y_size = part_y_size * y_parts + borders[1] + borders[3]
    surface = ImageSurface(FORMAT_RGB24, x_size, y_size)
    context = Context(surface)
    context.set_source_rgb(255, 255, 255)
    context.paint()
    for x in range(x_parts):
        for y in range(y_parts):
            image_surface = ImageSurface.create_from_png("part%.2d%.2d.png" % (x, y))
            context.set_source_surface(image_surface, x * part_x_size + borders[0],
              (y_parts - y - 1) * part_y_size + borders[3])
            context.paint()
            os.remove("part%.2d%.2d.png" % (x, y))

    # Draw border
    context.set_source_rgb(0, 0, 0)
    context.set_line_width(frame_line_width)
    context.rectangle(borders[0], borders[3], x_size - borders[0] - borders[2],
      y_size - borders[1] - borders[3])
    context.stroke()

    # Draw title.
    context.select_font_face("Nimbus Sans", FONT_SLANT_NORMAL, FONT_WEIGHT_BOLD)
    context.set_font_size(title_font_size)
    _, _, width, height, _, _ = context.text_extents(title)
    context.move_to((x_size - width) * 0.5, title_offset)
    context.show_text(title)

    # Draw attribution.
    context.set_font_size(attribution_font_size)
    _, _, width, height, _, _ = context.text_extents(attribution)
    context.move_to((x_size - width) * 0.5, y_size - attribution_offset)
    context.show_text(attribution)

    # Save to target file.
    surface.write_to_png(poster_filename)
    print "done."
