构建一个轻量、易部署的地理数据分发服务,定位类似于精简版的 tegola / pg_tileserv / tileserver-gl:
- 单一静态二进制(纯 Go、无 CGO),
scp上去就能跑,也可 Docker 一键部署 - 支持矢量切片(Mapbox Vector Tile / MVT)
- 支持 OGC 常用格式与接口:OGC API - Features(GeoJSON)、WMTS、GeoPackage、MVT(OGC 社区标准)
- 支持多数据源并做统一转换:数据库(PostGIS / MySQL 8 / MariaDB)与静态文件(PMTiles / MBTiles / GeoJSON / GeoPackage)
- 内置缓存、CORS、健康检查与数据源/服务目录/运行统计 WebUI,配置文件驱动,零外部运行时依赖
┌────────────────────────────────────────────┐
│ HTTP Server │
│ (net/http, Go 1.22+ pattern routing) │
│ │
客户端 │ /tiles/{layer}/{z}/{x}/{y}.pbf XYZ 切片 │
MapLibre ──────► │ /tiles/{layer}.json TileJSON │
OpenLayers │ /wmts/1.0.0/... WMTS │
QGIS │ /collections/... OGC API Features │
Leaflet │ /, /conformance, /health 服务元数据 │
└───────────────┬────────────────────────────┘
│ middleware: 日志 / CORS / gzip / recover
▼
┌────────────────────────────────────────────┐
│ Tile Cache (LRU+TTL) │
└───────────────┬────────────────────────────┘
▼
┌────────────────────────────────────────────┐
│ Source Registry(统一抽象) │
│ TileSource / FeatureSource 两个接口 │
└──┬────────┬────────┬────────┬────────┬────────┘
▼ ▼ ▼ ▼ ▼
PostGIS MySQL PMTiles MBTiles GeoJSON/GeoPackage
(ST_AsMVT) (bbox+MVT) (归档) (预切片) (内存引擎)
数据库 数据库 静态文件 静态文件 静态文件
两个核心接口(internal/source/source.go):
// TileSource 提供 z/x/y 切片(矢量或栅格)
type TileSource interface {
Tile(ctx context.Context, z, x, y uint32) ([]byte, error)
TileInfo() TileInfo // 格式、zoom 范围、bounds、图层描述
}
// FeatureSource 提供 OGC API - Features 要素查询
type FeatureSource interface {
Features(ctx context.Context, q FeatureQuery) (*FeatureCollection, error)
Feature(ctx context.Context, id string) (*Feature, error)
CollectionInfo() CollectionInfo
}一个数据源可以同时实现两个接口(如 PostGIS、MySQL、GeoJSON、GeoPackage),
也可以只实现其一(PMTiles、MBTiles 只做 TileSource)。PMTiles 另实现
ArchiveSource,供 HTTP 层以独立文件句柄提供 Range/206 原始归档。
| 数据源 | 类型 | 切片方式 | 要素查询 | 依赖 |
|---|---|---|---|---|
| PostGIS | 数据库 | 下推 ST_AsMVT/ST_TileEnvelope 动态生成 |
SQL + ST_AsGeoJSON,bbox 下推 |
jackc/pgx(纯 Go) |
| MySQL 8 / MariaDB | 数据库 | MBRIntersects bbox 下推,只取当前瓦片候选要素,再复用 memengine 编码 gzip MVT |
SQL + ST_AsGeoJSON,bbox/分页下推 |
go-sql-driver/mysql(纯 Go) |
| PMTiles | 静态文件 | v3 header + Hilbert tile id + root/leaf directory 定位;亦可 Range 直读归档 | 不支持 | 标准库(none/gzip) |
| MBTiles | 静态文件 | 直接读预切片(vector pbf 或 png/jpg/webp 栅格),TMS y 翻转 | 不支持 | modernc.org/sqlite(纯 Go) |
| GeoJSON | 静态文件 | 启动时载入内存引擎,动态裁剪+简化+编码 MVT | 内存 bbox 过滤 | paulmach/orb |
| GeoPackage | 静态文件 | 启动时解析 GPB→WKB 载入同一内存引擎 | 同上 | modernc.org/sqlite + orb/encoding/wkb |
GeoJSON 与 GeoPackage 共享同一个内存引擎:
- 启动时把要素加载进内存,逐要素预计算 bbox,构建轻量网格索引
- 切片请求:bbox 粗筛 → 按 zoom 做 Douglas-Peucker 简化 → 裁剪到 tile
(带 buffer)→
orb/encoding/mvt编码 → gzip - 要素请求:bbox 过滤 + limit/offset 分页,输出 GeoJSON
- 定位是"中小规模静态数据"(十万级要素以内),大数据请用 PostGIS 或预切 MBTiles
MySQL 数据仍是逐请求动态查询,不做全表内存快照。单瓦片最多接受 50,000 个候选要素;
超出时返回明确错误,防止低 zoom 意外拉取整张大表。连接器启动时读取服务版本并适配
MySQL/MariaDB 方言:MySQL 8 使用 axis-order=long-lat 与 ST_Transform;
MariaDB 11 不支持这两项扩展,因此 bbox 构造使用二参数形式,且仅接受 SRID 0/4326。
| 路径 | 说明 |
|---|---|
GET / |
Landing page:服务信息 + 全部链接(self/service-desc/service-doc/conformance/data 等标准 rel,由 internal/server/links.go 统一构造) |
GET /api |
OpenAPI 3.0 服务描述;internal/server/openapi.yaml 是唯一来源,GET /api 按 Accept/?f= 派生 JSON 或 YAML,两者不会互相漂移 |
GET /conformance |
OGC API 一致性声明;每条 class 都有 internal/server/conformance_test.go 里注册的自动化证据,缺证据的声明或没声明的证据都会让测试失败,详见 CONFORMANCE.md |
GET /health |
健康检查(含各数据源状态) |
GET /catalog |
全部图层/集合清单(便于前端发现) |
| 路径 | 说明 |
|---|---|
GET /tiles/{layer}/{z}/{x}/{y}.{ext} |
XYZ 切片,ext = pbf / mvt / png / jpg / webp |
GET /tiles/{layer}.json |
TileJSON 3.0 元数据 |
GET /archives/{layer}.pmtiles |
PMTiles 原始归档(HEAD、Range/206、条件请求) |
GET /wmts/1.0.0/WMTSCapabilities.xml |
WMTS 能力文档(RESTful) |
GET /wmts/1.0.0/{layer}/default/GoogleMapsCompatible/{z}/{y}/{x}.{ext} |
WMTS RESTful GetTile |
坐标体系:Web Mercator(EPSG:3857),tile matrix set 为
GoogleMapsCompatible(即 OGC 的 WebMercatorQuad)。
| 路径 | 说明 |
|---|---|
GET /collections |
集合列表 |
GET /collections/{id} |
集合描述(extent、links;有对应瓦片图层时附 rel=tiles 链接) |
GET /collections/{id}/items?bbox=&limit=&offset= |
要素查询,GeoJSON FeatureCollection |
GET /collections/{id}/items/{fid} |
单要素 |
| 路径 | 说明 |
|---|---|
GET /tileMatrixSets |
支持的 TileMatrixSet 列表 |
GET /tileMatrixSets/{id} |
TileMatrixSet 定义(目前仅 WebMercatorQuad,0-24 级,internal/server/tilematrixsets.go 按 OGC 公式计算而非硬编码表) |
GET /collections/{id}/tiles |
Tileset 资源:边界、tileMatrixSetLimits、瓦片 URL 模板(rel=item) |
GET /collections/{id}/tiles/{tileMatrixSetId}/{tileMatrix}/{tileRow}/{tileCol} |
标准取瓦片路由 |
这一节是 4.2 节 XYZ/WMTS 路由之外的加法,不是替代:标准路由内部直接
调用与 XYZ 相同的 serveTile(internal/server/tiles_ogc.go),两者对同一
z/x/y 返回字节完全一致(TestStandardTileMatchesXYZBytes 断言),缓存、
ETag、gzip passthrough 行为不分叉成两套实现。路径参数刻意用
tileMatrix/tileRow/tileCol 三个独立命名(对应 z/y/x),不复用 XYZ 路由
的 {z}/{x}/{y} mux pattern,避免两套语义混在一起难以推理。
两级结构(internal/cache),key = layer/z/x/y:
- 一级(内存):LRU + TTL,按条目数上限淘汰
- 二级(磁盘,可选):普通文件存储(
dir/layer/z/x/y),临时文件 + rename 原子写入;按 mtime 判定 TTL 过期;janitor 协程每 5 分钟清理 过期项与孤儿临时文件,超过max_size_mb时按最旧优先淘汰到 90% 水位。 磁盘命中会回填(promote)到内存一级。 - key 段经过白名单校验(并显式拒绝
./..),杜绝路径穿越 - MBTiles 本身就是本地文件读取,标记
Cacheable=false跳过缓存 - 响应带
ETag(内容哈希),支持If-None-Match→ 304
简单 API Key 方案(auth.enabled):middleware 层拦截,/health 豁免
(负载均衡探活)。key 可来自配置或 GEOVERSE_API_KEYS 环境变量;接受
Authorization: Bearer、X-API-Key 头或 api_key query 参数。比较时
先做 SHA-256 摘要再常数时间比较,避免时序侧信道。定位是"轻量内网/小
团队"场景;OIDC、限流等交由前置反向代理。
mcp.enabled 开启后在 mcp.path(默认 /mcp)暴露 Model Context
Protocol 服务(internal/mcpserver):
- 传输:Streamable HTTP,无状态模式(单 POST → 单 JSON 响应,不提供 SSE 流;GET 返回 405,符合规范中 server MAY not support 的约定)
- 协议:JSON-RPC 2.0,实现
initialize/ping/tools/list/tools/call,协议版本协商支持 2024-11-05 / 2025-03-26 / 2025-06-18 - 工具:
list_layers、describe_layer、query_features、get_feature、server_status;结果同时以 text 与 structuredContent 返回 - 零第三方依赖(手写 ~200 行 handler),复用 registry 的既有抽象; 鉴权 middleware 覆盖 MCP 端点
算法是注册即暴露的插件(HTTP POST /algorithms/{name} + MCP
algo_{name} 工具),详见 ALGORITHMS.md。已实现:
shortest_path(A*,室内多层)、isochrone(边内插 + marching squares)、
map_match(Newson-Krumm HMM/Viterbi)、dbscan(网格加速)。路由类算法
共享 networks 配置构建的可路由图(懒构建、进程内缓存)。
单个 YAML 文件(config.yaml),-config 指定路径:
server:
host: 0.0.0.0
port: 8080
cors: true
cache:
enabled: true
max_entries: 10000
ttl: 5m
assets:
root: ./data
enforce_root: true
max_file_size_mb: 8192
max_memory_file_size_mb: 256
data_sources:
allowed_schemas: [gis]
allowed_tables: [gis.roads, gis.warehouse]
require_readonly_role: true
sources:
- name: roads # PostGIS 动态矢量切片
type: postgis
dsn: postgres://user:pass@localhost:5432/gis
table: public.roads
geometry_column: geom
id_column: gid
srid: 4326
fields: [name, class]
min_zoom: 0
max_zoom: 22
- name: basemap # 预切好的 MBTiles
type: mbtiles
path: ./data/basemap.mbtiles
- name: warehouses # MySQL 8 / MariaDB 动态矢量切片
type: mysql
dsn: mysql://reader:pass@localhost:3306/gis
table: gis.warehouse
geometry_column: location
id_column: id
srid: 4326
- name: regional # PMTiles v3 单文件归档
type: pmtiles
path: ./data/regional.pmtiles
- name: pois # 静态 GeoJSON
type: geojson
path: ./data/pois.geojson
- name: parcels # OGC GeoPackage
type: geopackage
path: ./data/parcels.gpkg
layer: parcelsPostGIS 的 id_column 可配置 UUID/text 主键用于 OGC Features 单要素寻址,并作为普通 MVT 属性输出;只有 smallint/integer/bigint 列会写入 MVT 原生 feature id。动态瓦片查询会先把带 buffer 的 Web Mercator envelope 裁到全球合法范围,再变换到源 SRID,避免低 zoom 边缘瓦片跨反经线后查询到相反半球。
assets 为 GeoJSON、GeoPackage、MBTiles 与 PMTiles 提供统一文件边界。root 是规范化
后的允许目录而非路径基准;实现会解析符号链接、拒绝非普通文件,并分别限制流式归档与
整文件入内存的数据源。未配置时保持历史兼容行为,由 geoverse -doctor 给出警告。
PMTiles Archive 每次请求重新打开并复核文件身份,避免服务启动后链接目标被替换。
data_sources(internal/config/datasource_policy.go)是 PostGIS/MySQL 的对应机制:
allowed_schemas/allowed_tables 在 registry.OpenWithPolicies 里于建立数据库连接
之前执行(schema/table 是纯字符串推导,不依赖网络),把"注册到了错误的表"这类配置
失误挡在建连之前;这是与 DSN 账号权限独立的第二层保护,不是权限机制本身。
require_readonly_role 只影响 -doctor:source.PrivilegeProbe 接口让 postgis/mysql
各自实现 ExcessPrivileges(ctx),PostGIS 用 has_table_privilege 逐项查
INSERT/UPDATE/DELETE/TRUNCATE/REFERENCES/TRIGGER;MySQL/MariaDB 并查
USER_PRIVILEGES/SCHEMA_PRIVILEGES/TABLE_PRIVILEGES 三个作用域(只查
TABLE_PRIVILEGES 会漏掉全局或库级授权——例如 MySQL 官方镜像用
MYSQL_USER/MYSQL_DATABASE 自动建的账号就是库级 ALL PRIVILEGES,不会出现在
TABLE_PRIVILEGES 里),并对 SCHEMA_PRIVILEGES.TABLE_SCHEMA 做 \_/\% 反转义
(MySQL 8 官方镜像的库级授权会把库名当作 LIKE 模式转义存储,手动执行的
GRANT ... ON db.* 不一定会转义;反转义在未转义时是空操作,两种形态都安全)。
CLI 诊断位于 internal/diagnostics:-doctor 检查完整部署,-inspect <name|all> 输出
选定源的资产、能力与元数据。-format json 使用带 schemaVersion 的稳定报告;诊断只读,
警告不改变退出码,错误退出 1。
cmd/geoverse/ 入口(flag 解析、优雅退出)
internal/config/ YAML 配置解析与校验
internal/config/datasource_policy.go PostGIS/MySQL 的 allowed_schemas/allowed_tables 白名单
internal/diagnostics/ doctor / inspect 只读诊断与文本、JSON 输出
internal/tilemath/ Web Mercator 切片数学(z/x/y ↔ bbox)
internal/cache/ 两级缓存(内存 LRU + 磁盘持久层)
internal/mcpserver/ MCP 端点(JSON-RPC / Streamable HTTP)
internal/source/ 接口定义 + registry(按配置构建数据源)
internal/source/postgis/ PostGIS 实现
internal/source/mysql/ MySQL 8 / MariaDB 实现(bbox 下推 + MVT 编码)
internal/source/mbtiles/ MBTiles 实现
internal/source/pmtiles/ PMTiles v3 读取器(XYZ + archive Range 分发)
internal/source/memengine/ 内存要素引擎(MVT 编码、要素查询)
internal/source/geojsonsrc/ GeoJSON 加载器 → memengine
internal/source/geopackage/ GeoPackage 加载器(GPB/WKB 解析)→ memengine
internal/server/ HTTP 服务、路由、handler、middleware、内嵌 WebUI
internal/server/openapi.yaml OpenAPI 3.0 文档源(GET /api 的唯一来源)
internal/server/conformance.go /conformance 声明的 class 常量与列表
internal/server/conformance_test.go 声明 ↔ 证据双向校验(TestConformanceClassesHaveEvidence)
internal/server/links.go landing/collections/tiles 共用的标准 link 构造
internal/server/tilematrixsets.go WebMercatorQuad TileMatrixSet 计算与端点
internal/server/tiles_ogc.go OGC API - Tiles tileset 资源 + 标准取瓦片路由(复用 serveTile)
internal/algo/ 算法插件框架(Algorithm/Registry/Env)
internal/algo/network/ 可路由图(多层、构图、索引、Dijkstra/A*)
internal/algo/routing/ 最短路径、等时圈、路径匹配
internal/algo/cluster/ DBSCAN
docs/ 设计文档
examples/ 示例数据与示例配置
- 不做栅格动态渲染(WMS GetMap 渲染引擎),栅格仅透传 MBTiles 已有切片
- 不做坐标系重投影服务(统一 WebMercatorQuad 输出,源数据支持 4326/3857)
- 鉴权仅到简单 API Key 一层;OIDC/配额/多租户由前置反向代理承担
make build→ 单二进制bin/geoverse(CGO_ENABLED=0,可静态运行于 scratch/alpine)Dockerfile多阶段构建,最终镜像 ~20MBdocker run -v ./data:/data -v ./config.yaml:/etc/geoverse/config.yaml -p 8080:8080 geoverse