博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
How it works(7) GDAL2Mbtiles源码阅读(A) 框架与存储
阅读量:4303 次
发布时间:2019-05-27

本文共 7955 字,大约阅读时间需要 26 分钟。

引入

gdal2Mbtiles是个(以下简称g2m),其作用是将栅格地图(主要是Tiff格式)切成瓦片,存入Mbtiles格式的数据库中,以便于其他支持Mbtiles格式的地图服务器直接调用.

一开始我也是为了用它来切割Tiff底图,发布Tileserver-GL服务的,不过用了一下,发现其切图速度比较快.所以想看一下其内部结构.觉得其代码并不简单,也是一个深思熟虑的系统.

整体架构

gdal2mbtiles.png

通观整体后会发现,g2m的面向对象设计做的很好.虽然最终只能输出png格式的图片,但实现了图片的基类和JPG的图片类,只能导出为Mbtiles格式却也能通过文件存储基类可以实现gdal2folder的功能.详尽的文档与充足的单元测试也说明了这是个成熟的用心的工具.

整个程序通过setup.py安装后,,主要负责构建g2m所需参数.

在python中,借助ArgumentParser处理参数是非常容易的事情:

parser=argparse.ArgumentParser()parser.add_argument("echo",help="echo the string")args=parser.parse_args()

add_argument()常用的参数:

dest:如果提供dest,例如dest=“a”,那么可以通过args.a访问该参数
default:设置参数的默认值
action:参数出发的动作
store:保存参数,默认
store_const:保存一个被定义为参数规格一部分的值(常量),而不是一个来自参数解析而来的值。
store_ture/store_false:保存相应的布尔值
append:将值保存在一个列表中。
append_const:将一个定义在参数规格中的值(常量)保存在一个列表中。
count:参数出现的次数
parser.add_argument("-v", “–verbosity”, action=“count”, default=0, help=“increase output verbosity”)
version:打印程序版本信息
type:把从命令行输入的结果转成设置的类型
choice:允许的参数值
parser.add_argument("-v", “–verbosity”, type=int, choices=[0, 1, 2], help=“increase output verbosity”)
help:参数命令的介绍

利用处理后的参数,就可以正式驱动g2m了.

def main(args=None, use_logging=True):    if args is None:        args = sys.argv[1:]    args = parse_args(args=args)    # 避免vips解析sys.argv    from gdal2Mbtiles.helpers import warp_Mbtiles    # 需要的话构建临时文件    with input_output(inputfile=args.INPUT,                      outputfile=args.OUTPUT) as (inputfile, outputfile):        # 记录元数据        metadata = dict(            description=args.description,            format=args.format,            name=args.name,            type=args.layer_type,            version=args.version,        )        # 通过GDAL初始化指定的空间参考        spatial_ref = SpatialReference.FromEPSG(args.spatial_reference)        # 初始化波段        if not args.coloring:            colors = band = None        else:            colors = args.coloring(args.colors)            band = args.colorize_band        # 初始化图片格式        pngdata = {
'png8': args.png8} # 开始切割 warp_Mbtiles(inputfile=inputfile.name, outputfile=outputfile.name, # MBTiles metadata=metadata, # GDAL相关参数 spatial_ref=spatial_ref, resampling=args.resampling, # 参数渲染 min_resolution=args.min_resolution, max_resolution=args.max_resolution, fill_borders=args.fill_borders, zoom_offset=args.zoom_offset, pngdata=pngdata, # 颜色处理 colors=colors, band=band) return 0

对于输入/输出路径,这里做了特殊的预处理.其值默认为系统输入/输出,如果未指定该值,则建立临时文件.

@contextmanagerdef input_output(inputfile, outputfile):    tempfiles = []    infile = inputfile    if inputfile == sys.stdin:        # 建立临时文件        infile = NamedTemporaryFile()        # 将数据从输入流复制到该文件        copyfileobj(inputfile, infile)        # 游标归0        infile.seek(0)        tempfiles.append(infile)    outfile = outputfile    if outputfile == sys.stdout:        outfile = NamedTemporaryFile()        tempfiles.append(outfile)    try:        yield infile, outfile        # 最终从临时文件输出到输出流        if outputfile == sys.stdout:            copyfileobj(open(outfile.name, 'rb'), outputfile)    finally:        for f in tempfiles:            f.close()

这里使用了contextmanager装饰器,将函数包装为一个支持with调用,结束后自动释放的对象.

通过给一个try…finally…结构的函数头部加上@contextmanager就可以通过with…as…结构来调用它了,这样try块中yield的数据被as出来,finally块中的数据在with…as…块结束的时候被执行。

这里默认的输入输出是系统的输入输出流.这看起来是很奇怪的,既然要用g2m处理栅格地图,输入的文件也应该是个图像文件.

其实这种实现可以使得g2m不单单作为一个闭环的工具,而作为一个由linux管道构成的工具链的一部分.在管道中,数据流从A产出,经由系统输出\输入进入g2m,处理过后再经过系统输出进入管道输出给C.

在看处于最核心的模块helper之前,需要看一下,helper所调用的都是哪些模块.

存储的实现.

主要实现了三种存储:

  • 单一文件夹内存储
  • 文件夹分级存储
  • Mbtiles存储

瓦片存储的功能由存储的基类定义:

class Storage(object):    def __init__(self, renderer, pool=None):        self.renderer = renderer        self.hasher = intmd5    def __enter__(self):        return self    def __exit__(self, type, value, traceback):        return    def get_hash(self, image):      # 获取哈希值,对于相同的瓦片,不进行重复存储.因为实际在小比例尺下,图片重复的概率会很大.      # 问题是,所有的哈希值都存于内存,当所切级数变大时,内存占用会很大,索引效率也会降低.        return self.hasher(image.write_to_memory())    def filepath(self, x, y, z, hashed):      # 文件路径,仅对文件型存储起作用        raise NotImplementedError()    def post_import(self, pyramid):      # 生成金字塔后执行,仅对Mbtiles这种需要元数据描述的存储起作用        pass    def save(self, x, y, z, image):    # 最重要的函数,保存瓦片,子类必须要实现        raise NotImplementedError()    def save_border(self, x, y, z):      # 默认的保存边框        self.save(x=x, y=y, z=z, image=self._border_image())    @classmethod    def _border_image(cls, width=TILE_SIDE, height=TILE_SIDE):      # 类方法,生成透明的外框        image = VImageAdapter.new_rgba(            width, height, ink=rgba(r=0, g=0, b=0, a=0)        )        image._buf = image        return image

因为g2m最终暴露的只有存储于Mbtiles中,那就来看一下Mbtiles的类是如何继承基类的:

class MbtilesStorage(Storage):    def __init__(self, renderer, filename, zoom_offset=0, seen=set(),                 **kwargs):        super(MbtilesStorage, self).__init__(renderer=renderer,                                             **kwargs)                                                     self.zoom_offset = zoom_offset        self.seen = seen        self._border_hashed = None        self.Mbtiles = None    # 不使用工厂模式,也会创建Mbtiles文件        if isinstance(filename, basestring):            self.filename = filename            self.Mbtiles = MBTiles(filename=filename)        else:            self.Mbtiles = filename            self.filename = self.Mbtiles.filename    def __del__(self):        if self.Mbtiles is not None:            self.Mbtiles.close()    def __exit__(self, type, value, traceback):        if self.Mbtiles is not None:            self.Mbtiles.close()    @classmethod    def create(cls, renderer, filename, metadata, zoom_offset=None,               version=None, **kwargs):        # 工厂模式创建Mbtiles文件        bounds = metadata.get('bounds', None)        if bounds is not None:            metadata['bounds'] = bounds.lower_left + bounds.upper_right        Mbtiles = MBTiles.create(filename=filename, metadata=metadata,                                 version=version)        return cls(renderer=renderer,                   filename=Mbtiles,                   zoom_offset=zoom_offset,                   **kwargs)    def post_import(self, pyramid):    # 源影像建金字塔完成后,给Mbtiles赋元数据        transform = pyramid.dataset.GetCoordinateTransformation(            dst_ref=SpatialReference.FromEPSG(4326)        )        lower_left, upper_right = pyramid.dataset.GetTiledExtents(            transform=transform        )        self.Mbtiles.metadata['bounds'] = (lower_left.x, lower_left.y,                                           upper_right.x, upper_right.y)    def save(self, x, y, z, image):        hashed = self.get_hash(image)        # 如果有重复的瓦片,就直接写入哈希值,而不是存储瓦片        if hashed in self.seen:            self.Mbtiles.insert(x=x, y=y,                                z=z + self.zoom_offset,                                hashed=hashed)        else:            self.seen.add(hashed)            contents = self.renderer.render(image)            if sys.version_info < (3, 0):                data = buffer(contents)            else:                data = memoryview(contents)            # 插入渲染后的瓦片            self.Mbtiles.insert(x=x, y=y,                                z=z + self.zoom_offset,                                hashed=hashed,                                data=data)    def save_border(self, x, y, z):      # 同瓦片一样,透明的边框也不重复渲染存储        if self._border_hashed is None:            image = self._border_image()            self.save(x=x, y=y, z=z, image=image)            self._border_hashed = self.get_hash(image)        else:            self.Mbtiles.insert(x=x, y=y,                                z=z + self.zoom_offset,                                hashed=self._border_hashed)

我们能看到,在存储瓦片到Mbtiles时,有两种方法:

  • 存储x,y,z+图像+图像的哈希值
  • 存储x,y,z+图像的哈希值

Mbtiles设计上的特性就是不存储重复的瓦片.因为它本质上是Sqlite数据库,里面存储有行列号索引表和瓦片图像索引表,

对于相同的瓦片,只要通过相同的瓦片索引,就能关联起来,可以节省大量的重复瓦片空间占用.

转载地址:http://jgqws.baihongyu.com/

你可能感兴趣的文章
hive常用函数及数据结构介绍
查看>>
Hive面试题干货(亲自跟着做了好几遍,会了的话对面试大有好处)
查看>>
力扣题解-230. 二叉搜索树中第K小的元素(递归方法,中序遍历解决)
查看>>
力扣题解-123. 买卖股票的最佳时机 III(动态规划)
查看>>
Django 源码阅读:服务启动(wsgi)
查看>>
Django 源码阅读:url解析
查看>>
Docker面试题(一)
查看>>
第一轮面试题
查看>>
2020-11-18
查看>>
Docker面试题(二)
查看>>
一、redis面试题及答案
查看>>
消息队列2
查看>>
C++ 线程同步之临界区CRITICAL_SECTION
查看>>
测试—自定义消息处理
查看>>
MFC中关于虚函数的一些问题
查看>>
根据图层名获取图层和图层序号
查看>>
规范性附录 属性值代码
查看>>
提取面狭长角
查看>>
Arcsde表空间自动增长
查看>>
Arcsde报ora-29861: 域索引标记为loading/failed/unusable错误
查看>>