博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
How it works(6) TileStache源码阅读(B) 自带功能模块
阅读量:4303 次
发布时间:2019-05-27

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

引入

TileStache的核心阅读完了,就可以看看具体的功能部分是如何运行的.

功能部分有4大类:

模块

缓存的使用在上一篇已经看过了:

  • 读取缓存不需要锁
  • 写入缓存必须先加锁,再写入,再解锁

所有的缓存都必须实现如下的方法:

  • save
  • read
  • lock/unlock
  • remove

下面以文件缓存为例:

class Disk:    def __init__(self, path, umask=0o022, dirs='safe', gzip='txt text json xml'.split()):        self.cachepath = path        self.umask = int(umask)        self.dirs = dirs        self.gzip = [format.lower() for format in gzip]    def _is_compressed(self, format):        return format.lower() in self.gzip        def _filepath(self, layer, coord, format):       """       获取缓存路径       """        l = layer.name()        z = '%d' % coord.zoom        e = format.lower()        e += self._is_compressed(format) and '.gz' or ''        # safe模式下,文件名进行扩充,一个文件夹下将尽可能包含少的文件        # 如12/000/656/001/582.png        if self.dirs == 'safe':            x = '%06d' % coord.column            y = '%06d' % coord.row            # 将行列号扩展为6位            x1, x2 = x[:3], x[3:]            y1, y2 = y[:3], y[3:]                        filepath = os.sep.join( (l, z, x1, x2, y1, y2 + '.' + e) )        # portable模式下不作处理        # 如12/656/1582.png        elif self.dirs == 'portable':            x = '%d' % coord.column            y = '%d' % coord.row            filepath = os.sep.join( (l, z, x, y + '.' + e) )                    elif self.dirs == 'quadtile':           # 实现quadtile索引模式,详见https://wiki.openstreetmap.org/wiki/QuadTiles            pad, length = 1 << 31, 1 + coord.zoom            xs = bin(pad + int(coord.column))[-length:]            ys = bin(pad + int(coord.row))[-length:]            dirpath = ''.join([str(int(y+x, 2)) for (x, y) in zip(xs, ys)])            parts = [dirpath[i:i+3] for i in range(0, len(dirpath), 3)]            filepath = os.sep.join([l] + parts[:-1] + [parts[-1] + '.' + e])                return filepath    def _fullpath(self, layer, coord, format):     """     获取完整缓存路径     """        filepath = self._filepath(layer, coord, format)        fullpath = pathjoin(self.cachepath, filepath)        return fullpath    def _lockpath(self, layer, coord, format):        return self._fullpath(layer, coord, format) + '.lock'        def lock(self, layer, coord, format):        lockpath = self._lockpath(layer, coord, format)        due = time.time() + layer.stale_lock_timeout                while True:            # 如果出错就不断重复尝试上锁            try:                umask_old = os.umask(self.umask)                # 如果超时还未加上锁就尝试先删除这个锁                if time.time() > due:                    try:                        os.rmdir(lockpath)                    except OSError:                        pass                                os.makedirs(lockpath, 0o777&~self.umask)                break            except OSError as e:                if e.errno != 17:                    raise                time.sleep(.2)            finally:               # 恢复为原先的权限状态                os.umask(umask_old)        def unlock(self, layer, coord, format):        lockpath = self._lockpath(layer, coord, format)        # 解锁就是删掉锁文件        try:            os.rmdir(lockpath)        except OSError:            pass            def remove(self, layer, coord, format):        fullpath = self._fullpath(layer, coord, format)        try:            os.remove(fullpath)        except OSError as e:            if e.errno != 2:                raise            def read(self, layer, coord, format):        fullpath = self._fullpath(layer, coord, format)                if not exists(fullpath):            return None        # 当前缓存的存活周期        age = time.time() - os.stat(fullpath).st_mtime        # 超时则无效        if layer.cache_lifespan and age > layer.cache_lifespan:            return None        elif self._is_compressed(format):            return gzip.open(fullpath, 'r').read()        else:            body = open(fullpath, 'rb').read()            return body        def save(self, body, layer, coord, format):        fullpath = self._fullpath(layer, coord, format)                try:            umask_old = os.umask(self.umask)            os.makedirs(dirname(fullpath), 0o777&~self.umask)        except OSError as e:            if e.errno != 17:                raise        finally:            os.umask(umask_old)        suffix = '.' + format.lower()        suffix += self._is_compressed(format) and '.gz' or ''        # 先写入临时文件        fh, tmp_path = mkstemp(dir=self.cachepath, suffix=suffix)        # 处理压缩        if self._is_compressed(format):            os.close(fh)            tmp_file = gzip.open(tmp_path, 'w')            tmp_file.write(body)            tmp_file.close()        else:            os.write(fh, body)            os.close(fh)        # 再移动到正确位置        try:            os.rename(tmp_path, fullpath)        except OSError:            os.unlink(fullpath)            os.rename(tmp_path, fullpath)        os.chmod(fullpath, 0o666&~self.umask)

从前Core.py对Provider的调用可以看出,只要一个Provider拥有renderArea这一方法,就能完成瓦片的渲染:

if self.doMetatile() or hasattr(provider, 'renderArea'):            tile = provider.renderArea(width, height, srs, xmin, ymin, xmax, ymax, coord.zoom)        elif hasattr(provider, 'renderTile'):            width, height = self.dim, self.dim            tile = provider.renderTile(width, height, srs, coord)

就以mapnik的Provider为例:

class ImageProvider:        def __init__(self, layer, mapfile, fonts=None, scale_factor=None):        """        初始化mapnik引擎        """        maphref = urljoin(layer.config.dirpath, mapfile)        scheme, h, path, q, p, f = urlparse(maphref)        if scheme in ('file', ''):            self.mapfile = path        else:            self.mapfile = maphref        self.layer = layer        self.mapnik = None        try:            engine = mapnik.FontEngine.instance()        except AttributeError:            engine = mapnik.FontEngine        if fonts:            fontshref = urljoin(layer.config.dirpath, fonts)            scheme, h, path, q, p, f = urlparse(fontshref)            if scheme not in ('file', ''):                raise Exception('Fonts from "%s" can\'t be used by Mapnik' % fontshref)            for font in glob(path.rstrip('/') + '/*.ttf'):                engine.register_font(str(font))        self.scale_factor = scale_factor    @staticmethod    def prepareKeywordArgs(config_dict):        """        静态函数,从配置文件发过来的参数,转换成自身所需的形式        """        kwargs = {
'mapfile': config_dict['mapfile']} if 'fonts' in config_dict: kwargs['fonts'] = config_dict['fonts'] if 'scale factor' in config_dict: kwargs['scale_factor'] = int(config_dict['scale factor']) return kwargs def renderArea(self, width, height, srs, xmin, ymin, xmax, ymax, zoom): """ 渲染给定区域 """ start_time = time() if global_mapnik_lock.acquire(): try: if self.mapnik is None: self.mapnik = get_mapnikMap(self.mapfile) logging.debug('TileStache.Mapnik.ImageProvider.renderArea() %.3f to load %s', time() - start_time, self.mapfile) self.mapnik.width = width self.mapnik.height = height self.mapnik.zoom_to_box(Box2d(xmin, ymin, xmax, ymax)) img = mapnik.Image(width, height) if self.scale_factor is None: mapnik.render(self.mapnik, img) else: mapnik.render(self.mapnik, img, self.scale_factor) except: self.mapnik = None raise finally: # always release the lock global_mapnik_lock.release() if hasattr(Image, 'frombytes'): img = Image.frombytes('RGBA', (width, height), img.tostring()) else: img = Image.fromstring('RGBA', (width, height), img.tostring()) logging.debug('TileStache.Mapnik.ImageProvider.renderArea() %dx%d in %.3f from %s', width, height, time() - start_time, self.mapfile) return img

可以看出,整体结构非常简单,因为所有的复杂操作都交给mapnik本身了,只需传参,静等返回.

同mapnik类似,基本上是通过简单调用已经封装完善的库,没有太多操作,在此不再赘述.

总结

阅读完TileStache的感觉就是,它把更多的精力放在扩充专业功能上,而非架构的精妙,尽可能的描述了一个地图服务器应该有哪些GIS功能.

一点额外的想法

无论是Tilestrata还是TileStache,都是很不错的地图服务器,或许不会像Geoserver或Arcgis Server那么优秀,但足以满足很多人的需求了,但他们却并不为大多数人所知.

以Geoserver为例,推广的难度在于两点:

  1. 使用
    geoserver有一个web页面的控制台,先不说美观与否,它确实可以进行全部的操作,真正做到了开箱即用,而不是执行控制台命令,编写代码或配置文件,这足以吓退全部用户了.
  2. 环境
    geoserver的开箱即用包含一个很大的前提:它足够容易"开箱",配好java环境,就能运行.尽管如Tilestrata或TileStache因为调用C++库等拥有更高的效率,更低的内存占用,但安装还是太"硬核",还包含相当多不确定性甚至拒绝windows平台的用户.这也足以吓退全部用户了.

docker+一个简单H5界面+一个有基本功能的地图服务器或许就是一个更令人能接受的产品了.

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

你可能感兴趣的文章
PHP empty、isset、innull的区别
查看>>
apache+nginx 实现动静分离
查看>>
通过Navicat远程连接MySQL配置
查看>>
phpstorm开发工具的设置用法
查看>>
Linux 系统挂载数据盘
查看>>
Git基础(三)--常见错误及解决方案
查看>>
Git(四) - 分支管理
查看>>
PHP Curl发送数据
查看>>
HTTP协议
查看>>
HTTPS
查看>>
git add . git add -u git add -A区别
查看>>
apache下虚拟域名配置
查看>>
session和cookie区别与联系
查看>>
PHP 实现笛卡尔积
查看>>
Laravel中的$loop
查看>>
CentOS7 重置root密码
查看>>
Centos安装Python3
查看>>
PHP批量插入
查看>>
laravel连接sql server 2008
查看>>
Laravel 操作redis的各种数据类型
查看>>