示例#1
0
def merge_mbtiles(mbtiles_file1, mbtiles_file2, **kwargs):

    scale         = kwargs.get('tile_scale', None)
    zoom          = kwargs.get('zoom', -1)
    min_zoom      = kwargs.get('min_zoom', 0)
    max_zoom      = kwargs.get('max_zoom', 18)

    tmp_dir         = kwargs.get('tmp_dir', None)
    auto_commit     = kwargs.get('auto_commit', False)
    journal_mode    = kwargs.get('journal_mode', 'wal')
    synchronous_off = kwargs.get('synchronous_off', False)

    min_timestamp = kwargs.get('min_timestamp', 0)
    max_timestamp = kwargs.get('max_timestamp', 0)

    delete_after_export   = kwargs.get('delete_after_export', False)
    print_progress        = kwargs.get('progress', False)
    delete_vanished_tiles = kwargs.get('delete_vanished_tiles', False)
    flip_tile_y           = kwargs.get('flip_y', False)
    debug                 = kwargs.get('debug', False)

    if tmp_dir and not os.path.isdir(tmp_dir):
        os.mkdir(tmp_dir)

    if zoom >= 0:
        min_zoom = max_zoom = zoom


    check_before_merge = kwargs.get('check_before_merge', False)
    if check_before_merge and not check_mbtiles(mbtiles_file2, **kwargs):
        sys.stderr.write("The pre-merge check on %s failed\n" % (mbtiles_file2))
        sys.exit(1)


    con1 = mbtiles_connect(mbtiles_file1, auto_commit, journal_mode, synchronous_off, False, False)
    con2 = mbtiles_connect(mbtiles_file2, auto_commit, journal_mode, synchronous_off, False, True)

    con1.mbtiles_setup()

    # if not con1.is_compacted():
    #     sys.stderr.write('To merge two mbtiles databases, the receiver must already be compacted\n')
    #     con1.close()
    #     con2.close()
    #     sys.exit(1)

    if not con2.is_compacted() and (min_timestamp != 0 or max_timestamp != 0):
        con1.close()
        con2.close()
        sys.stderr.write('min-timestamp/max-timestamp can only be used with compacted databases.\n')
        sys.exit(1)


    zoom_level_string = None

    if min_zoom == max_zoom:
        zoom_level_string = "zoom level %d" % (min_zoom)
    else:
        zoom_level_string = "zoom levels %d -> %d" % (min_zoom, max_zoom)

    logger.info("Merging %s --> %s (%s)" % (prettify_connect_string(con2.connect_string), prettify_connect_string(con1.connect_string), zoom_level_string))


    # Check that the old and new image formats are the same
    original_format = new_format = None
    try:
        original_format = con1.metadata().get('format')
    except:
        pass

    try:
        new_format = con2.metadata().get('format')
    except:
        pass

    if new_format == None:
        logger.info("No image format found in the sending database, assuming 'png'")
        new_format = "png"

    if original_format != None and new_format != original_format:
        con1.close()
        con2.close()
        sys.stderr.write('The files to merge must use the same image format (png or jpg)\n')
        sys.exit(1)

    if original_format == None and new_format != None:
        con1.update_metadata("format", new_format)

    if new_format == None:
        new_format = original_format


    count = 0
    start_time = time.time()
    chunk = 1000

    total_tiles = 1

    if print_progress or debug:
        total_tiles = con2.tiles_count(min_zoom, max_zoom, min_timestamp, max_timestamp, scale)

        if total_tiles == 0:
            con1.close()
            con2.close()
            sys.stderr.write('No tiles to merge, exiting...\n')
            return

        logger.debug("%d tiles to merge" % (total_tiles))
        if print_progress:
            sys.stdout.write("%d tiles to merge\n" % (total_tiles))
            sys.stdout.write("0 tiles merged (0% @ 0 tiles/sec)")
            sys.stdout.flush()



    # merge and process (--merge --execute)
    if con2.is_compacted() and kwargs['command_list']:
        default_pool_size = kwargs.get('poolsize', -1)
        if default_pool_size < 1:
            default_pool_size = None
            logger.debug("Using default pool size")
        else:
            logger.debug("Using pool size = %d" % (default_pool_size))

        pool = Pool(default_pool_size)
        multiprocessing.log_to_stderr(logger.level)

        tiles_to_process = []
        known_tile_ids = {}

        for t in con2.tiles_with_tile_id(min_zoom, max_zoom, min_timestamp, max_timestamp, scale):
            tile_z = t[0]
            tile_x = t[1]
            tile_y = t[2]
            tile_scale = t[3]
            tile_data = str(t[4])
            tile_id = t[5]

            if flip_tile_y:
                tile_y = flip_y(tile_z, tile_y)

            new_tile_id = known_tile_ids.get(tile_id)
            if new_tile_id is None:
                tmp_file_fd, tmp_file_name = tempfile.mkstemp(suffix=".%s" % (new_format), prefix="tile_", dir=tmp_dir)
                tmp_file = os.fdopen(tmp_file_fd, "w")
                tmp_file.write(tile_data)
                tmp_file.close()

                tiles_to_process.append({
                    'tile_id':tile_id,
                    'filename':tmp_file_name,
                    'format':new_format,
                    'size':len(tile_data),
                    'command_list':kwargs['command_list'],
                    'tile_x':tile_x,
                    'tile_y':tile_y,
                    'tile_z':tile_z,
                    'tile_scale':tile_scale
                })
            else:
                con1.insert_tile_to_map(tile_z, tile_x, tile_y, tile_scale, new_tile_id)

                count = count + 1
                if (count % 100) == 0:
                    logger.debug("%d tiles merged (%.1f%% @ %.1f tiles/sec)" % (count, (float(count) / float(total_tiles)) * 100.0, count / (time.time() - start_time)))
                    if print_progress:
                        sys.stdout.write("\r%d tiles merged (%.1f%% @ %.1f tiles/sec)" % (count, (float(count) / float(total_tiles)) * 100.0, count / (time.time() - start_time)))
                        sys.stdout.flush()


            if len(tiles_to_process) < chunk:
                continue

            count = process_tiles(pool, tiles_to_process, con1, count, total_tiles, start_time, print_progress, delete_vanished_tiles, known_tile_ids)

            tiles_to_process = []

        if len(tiles_to_process) > 0:
            count = process_tiles(pool, tiles_to_process, con1, count, total_tiles, start_time, print_progress, delete_vanished_tiles, known_tile_ids)


    # merge from a compacted database (--merge)
    elif con2.is_compacted():
        known_tile_ids = set()

        tmp_images_list = []
        tmp_row_list = []
        tmp_tiles_list = []

        for t in con2.tiles_with_tile_id(min_zoom, max_zoom, min_timestamp, max_timestamp, scale):
            tile_z = t[0]
            tile_x = t[1]
            tile_y = t[2]
            tile_scale = t[3]
            tile_data = str(t[4])
            tile_id = t[5]

            if flip_tile_y:
                tile_y = flip_y(tile_z, tile_y)

            if con1.is_compacted():
                if tile_id not in known_tile_ids:
                    tmp_images_list.append( (tile_id, tile_data) )
                    known_tile_ids.add(tile_id)

                tmp_row_list.append( (tile_z, tile_x, tile_y, tile_scale, tile_id, int(time.time())) )
            else:
                tmp_tiles_list.append( (tile_z, tile_x, tile_y, tile_scale, tile_data, int(time.time())) )

            count = count + 1
            if (count % 100) == 0:
                logger.debug("%d tiles merged (%.1f%% @ %.1f tiles/sec)" % (count, (float(count) / float(total_tiles)) * 100.0, count / (time.time() - start_time)))
                if print_progress:
                    sys.stdout.write("\r%d tiles merged (%.1f%% @ %.1f tiles/sec)" % (count, (float(count) / float(total_tiles)) * 100.0, count / (time.time() - start_time)))
                    sys.stdout.flush()

            if len(tmp_images_list) > 250:
                con1.insert_tiles_to_images(tmp_images_list)
                tmp_images_list = []

            if len(tmp_row_list) > 250:
                con1.insert_tiles_to_map(tmp_row_list)
                tmp_row_list = []

            if len(tmp_tiles_list) > 250:
                con1.insert_tiles(tmp_tiles_list)
                tmp_tiles_list = []

        # Push the remaining rows to the database
        if len(tmp_images_list) > 0:
            con1.insert_tiles_to_images(tmp_images_list)

        if len(tmp_row_list) > 0:
            con1.insert_tiles_to_map(tmp_row_list)

        if len(tmp_tiles_list) > 0:
            con1.insert_tiles(tmp_tiles_list)


    # merge an uncompacted database (--merge)
    else:
        known_tile_ids = set()

        tmp_images_list = []
        tmp_row_list = []
        tmp_tiles_list = []

        for t in con2.tiles(min_zoom, max_zoom, min_timestamp, max_timestamp, scale):
            tile_z = t[0]
            tile_x = t[1]
            tile_y = t[2]
            tile_scale = t[3]
            tile_data = str(t[4])

            if flip_tile_y:
                tile_y = flip_y(tile_z, tile_y)

            # Execute commands
            if kwargs.get('command_list'):
                tile_data = execute_commands_on_tile(kwargs['command_list'], new_format, tile_data, tmp_dir)

            if con1.is_compacted():
                m = hashlib.md5()
                m.update(tile_data)
                tile_id = m.hexdigest()

                if tile_id not in known_tile_ids:
                    tmp_images_list.append( (tile_id, tile_data) )
                    known_tile_ids.add(tile_id)

                tmp_row_list.append( (tile_z, tile_x, tile_y, tile_scale, tile_id, int(time.time())) )
            else:
                tmp_tiles_list.append( (tile_z, tile_x, tile_y, tile_scale, tile_data, int(time.time())) )

            count = count + 1
            if (count % 100) == 0:
                logger.debug("%d tiles merged (%.1f%% @ %.1f tiles/sec)" % (count, (float(count) / float(total_tiles)) * 100.0, count / (time.time() - start_time)))
                if print_progress:
                    sys.stdout.write("\r%d tiles merged (%.1f%% @ %.1f tiles/sec)" % (count, (float(count) / float(total_tiles)) * 100.0, count / (time.time() - start_time)))
                    sys.stdout.flush()

            if len(tmp_images_list) > 250:
                con1.insert_tiles_to_images(tmp_images_list)
                tmp_images_list = []

            if len(tmp_row_list) > 250:
                con1.insert_tiles_to_map(tmp_row_list)
                tmp_row_list = []

            if len(tmp_tiles_list) > 250:
                con1.insert_tiles(tmp_tiles_list)
                tmp_tiles_list = []

        # Push the remaining rows to the database
        if len(tmp_images_list) > 0:
            con1.insert_tiles_to_images(tmp_images_list)

        if len(tmp_row_list) > 0:
            con1.insert_tiles_to_map(tmp_row_list)

        if len(tmp_tiles_list) > 0:
            con1.insert_tiles(tmp_tiles_list)

    if print_progress:
        sys.stdout.write('\n')

    logger.info("%d tiles merged (100.0%% @ %.1f tiles/sec)" % (count, count / (time.time() - start_time)))
    if print_progress:
        sys.stdout.write("%d tiles merged (100.0%% @ %.1f tiles/sec)\n" % (count, count / (time.time() - start_time)))
        sys.stdout.flush()


    if delete_after_export:
        logger.debug("WARNING: Removing merged tiles from %s" % (mbtiles_file2))

        con2.delete_tiles(min_zoom, max_zoom, min_timestamp, max_timestamp, scale)
        con2.optimize_database(kwargs.get('skip_analyze', False), kwargs.get('skip_vacuum', False))


    con1.close()
    con2.close()