#!/usr/bin/env python

# Inspired by generate_tiles.py, part of the Open Street Map project:
# http://svn.openstreetmap.org/applications/rendering/mapnik/generate_tiles.py

import multiprocessing
import os

import invar

DEFAULT_MIN_ZOOM = 9
DEFAULT_MAX_ZOOM = 12

MAX_QUEUE_SIZE = 20000

class IVTile(invar.InvarUtility):
    description = 'Render tiles for a given bounding box from a Mapnik2 XML configuration file.'

    def add_arguments(self):
        self.argparser.add_argument('lat_1', type=float, help="Most nortern latitude")
        self.argparser.add_argument('lon_1', type=float, help="Most western longitude")
        self.argparser.add_argument('lat_2', type=float, help="Most southern latitude")
        self.argparser.add_argument('lon_2', type=float, help="Most eastern longitude")
        self.argparser.add_argument('min_zoom', help="Minimum zoom level to render", type=int, default=DEFAULT_MIN_ZOOM)
        self.argparser.add_argument('max_zoom', help="Maximum zoom level to render", type=int, default=DEFAULT_MAX_ZOOM)
        
        self.argparser.add_argument('-g', '--grid', action="store_true", help="Force grid JSON to be rendered alongside each tile. If -k or -f options are provided, this is inferred and need not be specified.")
        self.argparser.add_argument('-k', '--key', help="For grid rendering, the column in the associated dataset which is a unique ID for a feature. If not specified, will use mapnik default ('__id__')")
        self.argparser.add_argument('-f', '--fields', help="For grid rendering, a comma separated list of fields associated with each feature which will be included in grid JSON.")
        
    def main(self):
        if not os.path.isdir(self.args.output_dir):
             os.mkdir(self.args.output_dir)

        tile_projection = invar.GoogleProjection(self.args.max_zoom) 

        ll0 = (self.args.lon_1, self.args.lat_1)
        ll1 = (self.args.lon_2, self.args.lat_2)

        tile_queues = []
        tile_queue = multiprocessing.JoinableQueue(maxsize=MAX_QUEUE_SIZE) 
        tile_count = 0

        for zoom in range(self.args.min_zoom, self.args.max_zoom + 1):
            px0 = tile_projection.fromLLtoPixel(ll0, zoom)
            px1 = tile_projection.fromLLtoPixel(ll1, zoom)

            tile_x1 = int(px0[0] / 256.0)
            tile_x2 = int(px1[0] / 256.0) + 1
            tile_y1 = int(px0[1] / 256.0)
            tile_y2 = int(px1[1] / 256.0) + 1

            zoom_dir = os.path.join(self.args.output_dir, str(zoom))

            if not os.path.isdir(zoom_dir):
                os.mkdir(zoom_dir)

            for tile_x in range(tile_x1, tile_x2):
                # Validate x coordinate
                if (tile_x < 0) or (tile_x >= 2 ** zoom):
                    continue

                x_dir = os.path.join(zoom_dir, str(tile_x))

                if not os.path.isdir(x_dir):
                    os.mkdir(x_dir)

                for tile_y in range(tile_y1, tile_y2):
                    # Validate y coordinate
                    if (tile_y < 0) or (tile_y >= 2 ** zoom):
                        continue

                    filename = os.path.join(x_dir, '%s.png' % str(tile_y))

                    # Submit tile to be rendered into the queue
                    t = (filename, tile_x, tile_y, zoom)

                    # Because mulitprocessing.JoinableQueue has a fixed maxsize, we instantiate
                    # an array of such queues and fill them iteratively
                    if tile_count != 0 and (tile_count % MAX_QUEUE_SIZE) == 0:
                        tile_queues.append(tile_queue)
                        tile_queue = multiprocessing.JoinableQueue(maxsize=MAX_QUEUE_SIZE)

                    tile_queue.put(t)
                    tile_count += 1

            # Append first (or last) queue to list
            if (tile_count % MAX_QUEUE_SIZE) != 0:
                tile_queues.append(tile_queue)

        print 'Using %i processes to render %i tiles' % (self.args.process_count, tile_count)

        processes = []
        try:
            fields = self.args.fields.split(",")
        except:
            fields = None
            
        for i in range(self.args.process_count):
            grid = (self.args.grid or self.args.fields or self.args.key)
            renderer = invar.TileRenderer(tile_queues, self.args.config, self.args.width, self.args.height, buffer_size=self.args.buffer, skip_existing=self.args.skip_existing, grid=grid, key=self.args.key, fields=fields)
            renderer.start()

            processes.append(renderer)

        try:
            for tile_queue in tile_queues:
                tile_queue.join()
        except KeyboardInterrupt:
            for p in processes:
                p.terminate()

if __name__ == "__main__":
    ivtile = IVTile()
    ivtile.main()

