bin/pypngtile
author Tero Marttila <terom@qmsk.net>
Sat, 15 Jul 2017 11:42:22 +0300
changeset 183 1f56a81f0c69
parent 88 5cc2d044d368
permissions -rwxr-xr-x
Dockerfile.tileserver: tweak /srv/pngtile/images path
#!/usr/bin/env python

"""
    Python clone of the pngtile binary.
"""

import optparse, sys

import pypngtile as pt

# CLI options
options = None

def log (fmt, args) :
    print >>sys.stderr, fmt % args

def log_debug (fmt, *args) :
    if options.debug or options.verbose :
        log(fmt, args)

def log_info (fmt, *args) :
    if not options.quiet :
        log(fmt, args)

def log_warn (fmt, *args) :
    log(fmt, args)


def parse_hex (hex) :
    """
        Parse a 0xHH.. style string as a raw bytestring
    """

    if not hex.startswith("0x") :
        raise ValueError(hex)

    return hex[2:].decode("hex")

def parse_args (args) :
    """
        Parse given argv[1:]
    """

    global options

    parser = optparse.OptionParser(
            usage   = "Usage: %prog [options] <image file> [...]",
    )

    # build opts list
    parser.add_option('-q', "--quiet",          help="Supress informational output",                        action='store_true')
    parser.add_option('-v', "--verbose",        help="Display more output",                                 action='store_true')
    parser.add_option('-D', "--debug",          help="Equivalent to -v",                                    action='store_true')
    parser.add_option('-U', "--force-update",   help="Unconditionally update the image caches",             action='store_true')
    parser.add_option('-N', "--no-update",      help="Do not update the image caches, even if unusable",    action='store_true')
    parser.add_option('-B', "--background",     help="Background pattern for sparse cache file",            metavar="0xHH..")
    parser.add_option('-W', "--width",          help="Output tile width",                                   metavar="PX",       type='int', default=800)
    parser.add_option('-H', "--height",         help="Output tile height",                                  metavar="PX",       type='int', default=600)
    parser.add_option('-x', "--x",              help="Tile x offset",                                       metavar="PX",       type='int', default=0)
    parser.add_option('-y', "--y",              help="Tile y offset",                                       metavar="PX",       type='int', default=0)
    parser.add_option('-z', "--zoom",           help="Tile zoom level, -n",                                 metavar="ZL",       type='int', default=0)
    parser.add_option('-o', "--out",            help="Render tile to output file, or -",                    metavar="FILE")
    
    # parse
    options, args = parser.parse_args(args=args)

    # decode
    if options.background :
        try :
            options.background = parse_hex(options.background)

        except ValueError :
            raise ValueError("Invalid value for --background: %s" % options.background)

    return args


def process_tile (image) :
    """
        Process output tile for given image
    """
    
    # parse out
    if options.out == '-' :
        log_debug("\tUsing stdout as output...")
        
        # use stdout
        out = sys.stdout

    else :
        log_debug("\tOpening file for output: %s", options.out)

        # open file
        out = open(options.out, "wb")
    
    log_info("\tRender tile %dx%d@(%d:%d)@%d -> %s...", options.width, options.height, options.x, options.y, options.zoom, options.out)

    # render
    image.tile_file(options.width, options.height, options.x, options.y, options.zoom, out)
    
    log_debug("Rendered tile: %s", options.out)


def process_image (image) :
    """
        Process given image
    """

    # check cache status
    status = image.status()

    # update if stale?
    if status != pt.CACHE_FRESH or options.force_update :
        # decode status
        if status == pt.CACHE_NONE :
            log_info("\tImage cache is missing")
        
        elif status == pt.CACHE_STALE :
            log_info("\tImage cache is stale")

        elif status == pt.CACHE_INCOMPAT :
            log_info("\tImage cache is incompatible")

        elif status == pt.CACHE_FRESH :
            log_info("\tImage cache is fresh")

        else :
            log_warn("\tImage cache status unknown: %d", status)

        
        # update unless supressed
        if not options.no_update :
            log_info("\tUpdating image cache...")
            
            # update with optional background color
            image.update(background_color=options.background)

            log_debug("\tUpdated image cache")

        else :
            # warn
            log_warn("\tSupressing cache update even though it is needed")

    else: 
        log_debug("\tImage cache is fresh")

        # open it
        image.open()

    # show info
    info = image.info()

    log_info("\tImage dimensions: %d x %d (%d bpp)", info['img_width'], info['img_height'], info['img_bpp'])
    log_info("\tImage mtime=%d, bytes=%d", info['image_mtime'], info['image_bytes'])
    log_info("\tCache mtime=%d, bytes=%d, blocks=%d (%d bytes), version=%d", 
            info['cache_mtime'], info['cache_bytes'], info['cache_blocks'], info['cache_blocks'] * 512, info['cache_version']
    )
    

    # render tile?
    if options.out :
        log_debug("\tRendering output tile")

        process_tile(image)

    else :
        log_debug("\tNot rendering output tile")

def process_images (image_paths) :
    """
        Open up each image_path and process using process_image
    """

    for image_path in image_paths :
        log_debug("Loading image: %s", image_path)

        # open up
        image = pt.Image(image_path, pt.OPEN_UPDATE)

        log_info("Opened image: %s", image_path);

        # process
        process_image(image)


def main (args) :
    """
        Operate on given argv[1:]
    """

    # parse opts/args
    args = parse_args(args)
    
    # handle each image
    process_images(args)


if __name__ == '__main__' :
    from sys import argv

    main(argv[1:])