Terrocotta :A lightweight tile server|一个轻量瓦片服务器
Introduction
最近一直想找一个轻量的影像瓦片的服务端,上周一直在看@Vincent Sarago 基于其自己一套工具 rio-tiler , lambda-proxy 的的瓦片服务的provider,前前后后看了衍生的几个项目,包括lambda-tiler,landsat-tiler,rio-viz等等,经过简单的测试,感觉前两个工具均需要借助lambda才能发挥正常的性能,第三个应用框架用的tornado,考虑了并发问题,但是个单机应用,“移植”起来工程量挺大的,自己试了试放弃了。在单节点的情况下,请求阻塞问题非常严重,自己试着换了几个应用和服务端的组合,都没太大的改善。另外在单节点情况下,这种每个请求都要重新访问一次数据的方式并不经济。
简单的应用不行,在COG详情页看到了,Geotrellis项目,框架用scala实现的,在projects里发现了一个和需求很相近的实验项目,clone下来运行,并不能成功,好像是应用入口有变化,失败了,自己懒的上手改(不知道怎么改),就想着去quick start 里找个小例子,跑个tiles服务应该挺容易的(呸),scala在国内的新手使用体验是真的难,甚至比golang还难,构建工具sbt从maven中心仓库拉文件,乌龟似的启动速度,自己找了那寥寥无几的几篇更换国内源的方法,中间一度想吐🤮,最后换了华为云的源终于能接受了,sbt.build的诡异语法,硬着头皮坚持到io影像,最新版本的api根本跟docs大不一样了,自己照着api东改西改,又被魔鬼般的implict参数坑了十几分钟后:
$ rm -rf repos/geotrellis-test ~/.sbt $ brew rmtree sbt
溜了溜了。
Terracotta
Github的feed真是个好东西,替我推荐了好多有用的玩意,Terracotta也是(太难打了,就叫他陶罐吧)。官方描述如下:
Terracotta is a pure Python tile server that runs as a WSGI app on a dedicated webserver or as a serverless app on AWS Lambda. It is built on a modern Python 3.6 stack, powered by awesome open-source software such as Flask, Zappa, and Rasterio.
提供传统部署和Lambda两种方式,轻量,pure python,都挺符合我的口味,“技术栈”也相对新。
陶罐与同样基于函数计算的lambda-tiler相比,不管是从结构来讲,或是理解起来,都是后者更简单。后者的整个流程非常直接,基于COG的portion请求特性和GDAL的VFS(Virtual File Systems),不管你的数据在哪,多大,只要告诉我它数据的本地地址或者HTTP地址,它就可以实时的拉取切片。在lambda的环境下,这种方式在性能上不会有太大问题。但对于在国内使用、部署有两个问题。
- AWS在国内严重水土不服,给国内使用Lambda造成障碍,Aliyun等国内厂商也有函数计算的服务,但还不太成熟,移植proxy等成本也很高。
- 一些open access 的数据比如Landsat 8 ,Sentinel-2都托管在S3对象存储上,使用Lambda切片很大程度依赖在AWS各部件上快速访问,但如果在国内提供服务在访问速度上会受很大的影响。
当然,陶罐也是推荐部署在Lambda函数上的,的确,这种方式非常适合动态切片服务,但比Lambda-tiler,它加了一个易用、可靠的头文件的“缓存机制”。
在使用rio-tiler想实现一个可以快速部署在单机上、支持少用户,低请求的动态切片服务时,就曾经想在内存中对同源的数据的头文件缓存下来,因为每一张瓦片都要请求一次源数据获取头文件,在单机环境来看是很浪费的,当时自己的想法有建一个dict,根据数据源地址存储头文件或者建一个sqlite数据库来存储,试了建个dict的方式,但效果并不明显。
而陶罐在业务流程设计上就强制加入了这一点,这使得他在新增数据时会有一个预处理的过程,这比起直接处理有一个延后,但正所谓磨刀不误砍柴工,不得不说,这比起传统的预切片可要快出不少。
除此之外,对数据cog化,头文件注入等流程,陶罐都有很好的api支持。
Quick Start
试用非常简单,先切换到使用的环境,然后
$ pip install -U pip $ pip install terracotta
查看一下版本
$ terracotta --version $ terracotta, version 0.5.3.dev20+gd3e3da1
进入存放tif的目标文件夹,以cog格式对影像进行优化。
$ terracotta optimize-rasters *.tif -o optimized/
然后将希望serve的影像根据模式匹配存进sqlite数据库文件。
这里想吐槽一下这个功能,开始的时候我以为是一般的正则匹配,搞半天发现是{}的简单匹配,还不能不使用匹配,醉醉哒。
$ terracotta ingest optimized/LB8_{date}_{band}.tif -o test.sqlite
注入数据库完成后,启动服务
$ terracotta serve -d test.sqlite
服务默认在:5000启动,还提供了Web UI,需要另行启动,开另一个session:
$ terracotta connect localhost:5000
这样Web UI也就启动了。这样可以在提示的地址中访问到了。
Deployment
没看lambda的部署方式,因为大致和lambda-tiler方式差不多,因为国内aws访问半身不遂,移植到阿里云,腾讯云的serverless的成本又太高了,所以才放弃了这种方式。
传统的部署方式如下:
我是在centos的云主机上部署的,和docs里的大同小异。
首先新建环境,安装软件和依赖。
$ conda create --name gunicorn $ source activate gunicorn $ pip install cython $ git clone https://github.com/DHI-GRAS/terracotta.git $ cd /path/to/terracotta $ pip install -e . $ pip install gunicorn
准备数据,例子假设影像文件存放在/mnt/data/rasters/
$ terracotta optimize-rasters /mnt/data/rasters/*.tif -o /mnt/data/optimized-rasters $ terracotta ingest /mnt/data/optimized-rasters/{name}.tif -o /mnt/data/terracotta.sqlite
新建服务,这里自己踩了两个坑,官方例子使用的是nginx反向代理到sock的方式,自己试了多个方法,没成功,也不想深入了解了。
server { listen 80; server_name VM_IP; location / { include proxy_params; proxy_pass http://unix:/mnt/data/terracotta.sock; } }
另一个是,应用入口里的入口 版本更新过,service里的和上下文的不一样,修改之后如下
[Unit] Description=Gunicorn instance to serve Terracotta After=network.target [Service] User=root WorkingDirectory=/mnt/data Environment="PATH=/root/.conda/envs/gunicorn/bin" Environment="TC_DRIVER_PATH=/mnt/data/terracotta.sqlite" ExecStart=/root/.conda/envs/gunicorn/bin/gunicorn \ --workers 3 --bind 0.0.0.0:5000 -m 007 terracotta.server.app:app [Install] WantedBy=multi-user.target
另外一个地方,使用"0.0.0.0",使外网可以访问。
官方解释如下:
- Absolute path to Gunicorn executable
- Number of workers to spawn (2 * cores + 1 is recommended)
- Binding to a unix socket file
terracotta.sock
in the working directory- Dotted path to the WSGI entry point, which consists of the path to the python module containing the main Flask app and the app object:
terracotta.server.app:app
服务里需要指定Gunicorn的执行路径,设置workers数量,绑定socket file,指定应用入口。
设置开机启动,启动服务。
$ sudo systemctl start terracotta $ sudo systemctl enable terracotta $ sudo systemctl restart terracotta
这样就能看到服务的表述了。
$ curl localhost:5000/swagger.json
当然,也可以用terracotta自带的client来看一下效果:
$ terracotta connect localhost:5000
Workflow
对与头文件存储方式的选择,sqlite自然是更方便,但mysql的灵活性和稳定性更高了,在线数据可以实现远程注入。
这里碰到点问题,driver的create方法新建失败,自己没看出问题在哪,就从driver里找出表定义,手动新建所需表。
from typing import Tuple import terracotta as tc import pymysql # driver = tc.get_driver("mysql://root:password@ip-address:3306/tilesbox'") key_names = ('type', 'date', 'band') keys_desc = {'type': 'type', 'date': 'data\'s date', 'band': 'raster band'} _MAX_PRIMARY_KEY_LENGTH = 767 // 4 # Max key length for MySQL is at least 767B _METADATA_COLUMNS: Tuple[Tuple[str, ...], ...] = ( ('bounds_north', 'REAL'), ('bounds_east', 'REAL'), ('bounds_south', 'REAL'), ('bounds_west', 'REAL'), ('convex_hull', 'LONGTEXT'), ('valid_percentage', 'REAL'), ('min', 'REAL'), ('max', 'REAL'), ('mean', 'REAL'), ('stdev', 'REAL'), ('percentiles', 'BLOB'), ('metadata', 'LONGTEXT') ) _CHARSET: str = 'utf8mb4' key_size = _MAX_PRIMARY_KEY_LENGTH // len(key_names) key_type = f'VARCHAR({key_size})' with pymysql.connect(host='ip-address', user='root', password='password', port=3306, binary_prefix=True, charset='utf8mb4', db='tilesbox') as cursor: cursor.execute(f'CREATE TABLE terracotta (version VARCHAR(255)) ' f'CHARACTER SET {_CHARSET}') cursor.execute('INSERT INTO terracotta VALUES (%s)', [str('0.5.2')]) cursor.execute(f'CREATE TABLE key_names (key_name {key_type}, ' f'description VARCHAR(8000)) CHARACTER SET {_CHARSET}') key_rows = [(key, keys_desc[key]) for key in key_names] cursor.executemany('INSERT INTO key_names VALUES (%s, %s)', key_rows) key_string = ', '.join([f'{key} {key_type}' for key in key_names]) cursor.execute(f'CREATE TABLE datasets ({key_string}, filepath VARCHAR(8000), ' f'PRIMARY KEY({", ".join(key_names)})) CHARACTER SET {_CHARSET}') column_string = ', '.join(f'{col} {col_type}' for col, col_type in _METADATA_COLUMNS) cursor.execute(f'CREATE TABLE metadata ({key_string}, {column_string}, ' f'PRIMARY KEY ({", ".join(key_names)})) CHARACTER SET {_CHARSET}')
瓦罐的头文件存储共需要四个表。
Table | Describe |
---|---|
terracotta | 存储瓦罐版本信息 |
metadata | 存储数据头文件 |
Key_names | key类型及描述 |
Datasets | 数据地址及(key)属性信息 |
服务启动修改如下:
[Unit] Description=Gunicorn instance to serve Terracotta After=network.target [Service] User=root WorkingDirectory=/mnt/data Environment="PATH=/root/.conda/envs/gunicorn/bin" Environment="TC_DRIVER_PATH=root:password@ip-address:3306/tilesbox" Environment="TC_DRIVER_PROVIDER=mysql" ExecStart=/root/.conda/envs/gunicorn/bin/gunicorn \ --workers 3 --bind 0.0.0.0:5000 -m 007 terracotta.server.app:app [Install] WantedBy=multi-user.target
对于注入本地文件,可参照如下方法:
import os import terracotta as tc from terracotta.scripts import optimize_rasters, click_types import pathlib driver = tc.get_driver("/path/to/data/google/tc.sqlite") print(driver.get_datasets()) local = "/path/to/data/google/Origin.tiff" outdir = "/path/to/data/google/cog" filename = os.path.basename(os.path.splitext(local)[0]) seq = [[pathlib.Path(local)]] path = pathlib.Path(outdir) # 调用click方法 optimize_rasters.optimize_rasters.callback(raster_files=seq, output_folder=path, overwrite=True) outfile = outdir + os.sep + filename + ".tif" driver.insert(filepath=outfile, keys={'nomask': 'yes'}) print(driver.get_datasets())
运行如下
Optimizing rasters: 0%| | [00:00<?, file=Origin.tiff] Reading: 0%| | 0/992 Reading: 12%|█▎ | 124/992 Reading: 21%|██▏ | 211/992 Reading: 29%|██▉ | 292/992 Reading: 37%|███▋ | 370/992 Reading: 46%|████▌ | 452/992 Reading: 54%|█████▍ | 534/992 Reading: 62%|██████▏ | 612/992 Reading: 70%|██████▉ | 693/992 Reading: 78%|███████▊ | 771/992 Reading: 87%|████████▋ | 867/992 Creating overviews: 0%| | 0/1 Compressing: 0%| | 0/1 Optimizing rasters: 100%|██████████| [00:06<00:00, file=Origin.tiff] {('nomask',): '/path/to/data/google/nomask.tif', ('yes',): '/path/to/data/google/cog/Origin.tif'} Process finished with exit code 0
稍加修改就可以传入input 文件名 和 output的文件夹名,就能实现影像优化、注入的工作流。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南