Hello everyone.

First of all, I want to share with my two scripts for Mapnik rendering. First 
is a modified version of generate_tiles.py to use multithread rendering. Second 
is a modified generate_image.py to generate large bitmap posters like [1,2]. It 
uses tiled rendering so it's possible to render very large areas with high 
DPI. Only limitation is that whole poster must fit into computer's memory 
during merging and texts drawing because I don't know any image rendering 
library which can draw to memory mapped files or such. See scripts' code and 
comments or ask for detailed information.

Seconly, I would like to ask how should I properly extend an OpenStreetMap 
attribution on the [1,2] to include my name and to mention an OpenTrackMap 
project [3]. Could you give me an advice about this?

Thank you in advance.

[1] - http://blackhex.no-ip.org/raw-attachment/wiki/Files/poster_preview.png - 
OpenTrackMap poster, scaled down to 25% (13.4 MB)
[2] - http://blackhex.no-ip.org/raw-
attachment/wiki/Files/poster_small_preview.png - OpenTrackMap poster, scaled 
down to 10% (2.4 MB)
[3] - http://opentrackmap.no-ip.org/ - OpenTrackMap project homepage.

-- 
Ing. Radek Bartoň

Faculty of Information Technology
Department of Computer Graphics and Multimedia
Brno University of Technology

E-mail: black...@post.cz
Web: http://blackhex.no-ip.org
Jabber: black...@jabber.cz
#!/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 +nadgri...@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."
#!/usr/bin/python
# -*- coding: utf-8 -*-
from math import pi,cos,sin,log,exp,atan
from subprocess import call
from threading import Thread, Lock
from datetime import datetime
from time import sleep
import sys, os

os.environ['LD_LIBRARY_PATH'] = '/home/blackhex/local/Mapnik/lib'
path = ['/home/blackhex/local/Mapnik/lib/python2.6/site-packages']
path.extend(sys.path)
sys.path = path

DEG_TO_RAD = pi/180
RAD_TO_DEG = 180/pi

fsLock = Lock()
stdoutLock = Lock()

def minmax (a,b,c):
    a = max(a,b)
    a = min(a,c)
    return a

class GoogleProjection:
    def __init__(self,levels=18):
        self.Bc = []
        self.Cc = []
        self.zc = []
        self.Ac = []
        c = 256
        for d in range(0,levels):
            e = c/2;
            self.Bc.append(c/360.0)
            self.Cc.append(c/(2 * pi))
            self.zc.append((e,e))
            self.Ac.append(c)
            c *= 2

    def fromLLtoPixel(self,ll,zoom):
         d = self.zc[zoom]
         e = round(d[0] + ll[0] * self.Bc[zoom])
         f = minmax(sin(DEG_TO_RAD * ll[1]),-0.9999,0.9999)
         g = round(d[1] + 0.5*log((1+f)/(1-f))*-self.Cc[zoom])
         return (e,g)

    def fromPixelToLL(self,px,zoom):
         e = self.zc[zoom]
         f = (px[0] - e[0])/self.Bc[zoom]
         g = (px[1] - e[1])/-self.Cc[zoom]
         h = RAD_TO_DEG * ( 2 * atan(exp(g)) - 0.5 * pi)
         return (f,h)

from mapnik import *

def render_tiles(mapFile, threadId, bbox, tileSQDir, tileHQDir, minZoom = 1,
  maxZoom = 18, name = "unknown"):
    global fsLock, stdoutLock, finish
    print "Rendering: ", bbox, minZoom, maxZoom, name

    # Setup Mapnik
    gprj = GoogleProjection(maxZoom + 1) 
    m = Map(2 * 256, 2 * 256)
    load_map(m, mapFile)
    prj = 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 +nadgri...@null +no_defs +over")

    ll0 = (bbox[0],bbox[3])
    ll1 = (bbox[2],bbox[1])

    for z in range(minZoom,maxZoom + 1):
        px0 = gprj.fromLLtoPixel(ll0, z)
        px1 = gprj.fromLLtoPixel(ll1, z)

        # Check if we have directories in place.
        zoom = "%s" % z
        fsLock.acquire()
        if not os.path.exists(os.path.join(tileSQDir, zoom)):
            os.mkdir(os.path.join(tileSQDir, zoom))
        if not os.path.exists(os.path.join(tileHQDir, zoom)):
            os.mkdir(os.path.join(tileHQDir, zoom))
        fsLock.release()

        for x in range(int(px0[0]/256.0),int(px1[0]/256.0)+1):
            # Check if we have directories in place
            str_x = "%s" % x
            fsLock.acquire()
            if not os.path.exists(os.path.join(tileSQDir, zoom, str_x)):
                os.mkdir(os.path.join(tileSQDir, zoom, str_x))
            if not os.path.exists(os.path.join(tileHQDir, zoom, str_x)):
                os.mkdir(os.path.join(tileHQDir, zoom, str_x))
            fsLock.release()

            for y in range(int(px0[1]/256.0),int(px1[1]/256.0)+1):
                start_time = datetime.now()

                p0 = gprj.fromPixelToLL((x * 256.0, (y+1) * 256.0),z)
                p1 = gprj.fromPixelToLL(((x+1) * 256.0, y * 256.0),z)

                # render a new tile and store it on filesystem
                c0 = prj.forward(Coord(p0[0],p0[1]))
                c1 = prj.forward(Coord(p1[0],p1[1]))

                bbox = Envelope(c0.x,c0.y,c1.x,c1.y)
                bbox.width(bbox.width() * 2)
                bbox.height(bbox.height() * 2)
                m.zoom_to_box(bbox)

                str_y = "%s" % y

                tileHQFile = os.path.join(tileHQDir, zoom, str_x, str_y + '.png')
                tileSQFile = os.path.join(tileSQDir, zoom, str_x, str_y + '.png')

                exists = ""
                if os.path.exists(tileHQFile):
                    exists = "exists"
                else:
                    im = Image(512, 512)
                    render(m, im)
                    view = im.view(128,128,256,256) # x,y,width,height

                    fsLock.acquire()
                    # We should check file existance aggain for atomicity.
                    if not os.path.exists(tileHQFile):
                        view.save(tileHQFile,'png')
                    fsLock.release()

                    # This is not thread safe.
                    if not os.path.exists(tileSQFile):
                        call("convert -colors 255 %s %s" % (tileHQFile,
                          tileSQFile), shell = True)

                bytes = os.stat(tileHQFile)[6]
                empty = ""
                if bytes == 137:
                    empty = "empty tile"

                # Determine time of rendering.
                end_time = datetime.now()
                time_delta = end_time - start_time

                stdoutLock.acquire()
                print "Thread #" + str(threadId), time_delta, name, "[", \
                  minZoom, "-", maxZoom, "]: ", z, x, y, "p:", p0, p1, exists, \
                  empty
                stdoutLock.release()

                # Terminate thread if requested to finish.
                if finish:
                   return

class RenderThread(Thread):
    def __init__(self, mapFile, threadId, bbox, tileSQDir, tileHQDir, minZoom,
      maxZoom):
        Thread.__init__(self)
        self.mapFile = mapFile
        self.threadId = threadId
        self.bbox = bbox
        self.tileSQDir = tileSQDir
        self.tileHQDir = tileHQDir
        self.minZoom = minZoom
        self.maxZoom = maxZoom

    def run(self):
        render_tiles(self.mapFile, self.threadId, self.bbox, self.tileSQDir,
          self.tileHQDir, self.minZoom, self.maxZoom, "OpenStreetMap")

if __name__ == "__main__":
    try:
        mapFile = os.environ['MAPNIK_MAP_FILE']
    except KeyError:
        sys.stderr.write('MAPNIK_MAP_FILE environment variable is not set!')
        exit(1)
    try:
        tileSQDir = os.environ['MAPNIK_TILE_SQ_DIR']
    except KeyError:
        sys.stderr.write('MAPNIK_TILE_SQ_DIR environment variable is not set!')
        exit(1)
    try:
        tileHQDir = os.environ['MAPNIK_TILE_HQ_DIR']
    except KeyError:
        sys.stderr.write('MAPNIK_TILE_HQ_DIR environment variable is not set!')
        exit(1)
    try:
        numThreads = int(os.environ['MAPNIK_NUM_THREADS'])
    except KeyError:
        numThreads = 1

    # Czech Republic
    minZoom = 0
    maxZoom = 14
    bbox = (12.10, 48.55, 18.86, 51.06)

    # Create target directories.
    if not os.path.exists(tileSQDir):
        os.mkdir(tileSQDir)
    if not os.path.exists(tileHQDir):
        os.mkdir(tileHQDir)

    # Create and setup threads.
    finish = False
    threads = []
    threadLon = (bbox[2] - bbox[0]) / numThreads
    for threadId in range(numThreads):
       threadBbox = (bbox[0] + threadLon * threadId, bbox[1], bbox[0] +
         threadLon * (threadId + 1), bbox[3])
       thread = RenderThread(mapFile, threadId, threadBbox, tileSQDir, tileHQDir,
         minZoom, maxZoom)
       threads.append(thread)

    # Start the threads,
    for thread in threads:
        thread.start()

    # Wait for threads to finish.
    try:
        while not finish:
            for thread in threads:
                thread.join(1)
            my_finish = True
            for thread in threads:
                if thread.isAlive():
                    my_finish = False
                        break;
            finish = my_finish
    except KeyboardInterrupt:
        finish = True
        for thread in threads:
            thread.join()
_______________________________________________
dev mailing list
dev@openstreetmap.org
http://lists.openstreetmap.org/listinfo/dev

Reply via email to