本文共 8468 字,大约阅读时间需要 28 分钟。
TileStache的核心阅读完了,就可以看看具体的功能部分是如何运行的.
功能部分有4大类:缓存的使用在上一篇已经看过了:
所有的缓存都必须实现如下的方法:
下面以文件缓存为例:
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为例,推广的难度在于两点:
docker+一个简单H5界面+一个有基本功能的地图服务器或许就是一个更令人能接受的产品了.
转载地址:http://cgqws.baihongyu.com/