本文共 7955 字,大约阅读时间需要 26 分钟。
gdal2Mbtiles是个(以下简称g2m),其作用是将栅格地图(主要是Tiff格式)切成瓦片,存入Mbtiles格式的数据库中,以便于其他支持Mbtiles格式的地图服务器直接调用.
一开始我也是为了用它来切割Tiff底图,发布Tileserver-GL服务的,不过用了一下,发现其切图速度比较快.所以想看一下其内部结构.觉得其代码并不简单,也是一个深思熟虑的系统.通观整体后会发现,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所调用的都是哪些模块.
存储的实现.
主要实现了三种存储:瓦片存储的功能由存储的基类定义:
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时,有两种方法:
Mbtiles设计上的特性就是不存储重复的瓦片.因为它本质上是Sqlite数据库,里面存储有行列号索引表和瓦片图像索引表,
对于相同的瓦片,只要通过相同的瓦片索引,就能关联起来,可以节省大量的重复瓦片空间占用.转载地址:http://jgqws.baihongyu.com/