@deepinnet/service-mvt-middleware
v0.0.2
Published
NestJS middleware module for serving 2D MVT and custom 3D vector tile endpoints.
Readme
@deepinnet/service-mvt-middleware
NestJS 中间件包,用于把二维 MVT 和三维自定义 PBF 瓦片服务接到 HTTP 控制器层。
这个包只负责 NestJS 集成层:
- 暴露二维
/tiles/...路由 - 暴露三维
/tiles-3d/...路由 - 暴露
/tiles/:layer/metadata和/health - 把底层
@deepinnet/service-mvt-middleware-core引擎接到 Nest 应用
如果你不需要 NestJS,只想直接使用瓦片引擎和 PostgreSQL adapter,请改用:
@deepinnet/service-mvt-middleware-core
安装
pnpm add @deepinnet/service-mvt-middleware
pnpm add @deepinnet/service-mvt-middleware-core适用场景
- 你在 NestJS 中提供二维矢量瓦片接口
- 你在 NestJS 中提供三维自定义 PBF 接口
- 你希望把图层配置、控制器和健康检查统一放在一个 module 中
怎么使用
最小接入步骤通常是:
- 在数据库中准备好 PostGIS 图层表或视图。
- 在 Nest
AppModule中注册TileModule.forRoot(...)。 - 配置
layers、database、defaults。 - 启动应用后,通过
/tiles/...或/tiles-3d/...请求瓦片。
1. 在 Nest 中注册模块
import { Module } from "@nestjs/common";
import { TileModule } from "@deepinnet/service-mvt-middleware";
@Module({
imports: [
TileModule.forRoot({
layers: [
{
layer: "data",
sourceType: "table",
sourceObject: "public.geom",
idField: "id",
geomField: "geometry",
geom3dField: "geometry3d",
sourceCrs: "4326",
defaultOutputCrs: "gcj02",
supportedOutputCrs: ["4326", "gcj02"],
propertyWhitelist: ["name", "color"],
gzip: {
enabled: true,
},
clusterEnabled: true,
clusterMaxZoom: 15,
clusterCellSize: 256,
clusterMaxFeatures: 2048,
clusterFields: {
color: {
field: "color",
op: "top",
},
},
},
],
database: {
connectionString: process.env.DATABASE_URL,
},
defaults: {
sourceCrs: "4326",
defaultOutputCrs: "gcj02",
},
tile: {
extent: 4096,
buffer: 64,
maxFeatures: 20000,
},
controllers: {
tile: true,
tile3d: true,
metadata: true,
health: true,
},
}),
],
})
export class AppModule {}1.1 复用已有 pg.Pool
如果你的应用已经自己管理 PostgreSQL 连接池,可以直接把现有 pool 传给 database.pool:
import { Module } from "@nestjs/common";
import { Pool } from "pg";
import { TileModule } from "@deepinnet/service-mvt-middleware";
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
});
@Module({
imports: [
TileModule.forRoot({
layers: [
{
layer: "data",
sourceType: "table",
sourceObject: "public.geom",
idField: "id",
geomField: "geometry",
},
],
database: {
pool,
},
}),
],
})
export class AppModule {}这种方式下,瓦片查询和默认 health check 都会复用你传入的 pool。
连接池生命周期需要区分两种情况:
- 传
database.connectionString或database.poolConfig:模块内部会创建连接池,并在应用关闭时自动end() - 传
database.pool:连接池归调用方所有,这个包不会自动关闭,你需要在自己的关闭流程里手动执行await pool.end()
1.2 配置统一路由前缀
如果你希望把默认路由整体挂到统一前缀下,可以配置 routePrefix:
TileModule.forRoot({
routePrefix: "api/v1",
layers: [
{
layer: "data",
sourceType: "table",
sourceObject: "public.geom",
idField: "id",
geomField: "geometry",
},
],
})这样默认路由会变成:
/api/v1/tiles/:layer/:z/:x/:y.pbf/api/v1/tiles-3d/:layer/:z/:x/:y.pbf/api/v1/tiles/:layer/metadata/api/v1/health
routePrefix 会自动去掉首尾多余的 /,所以 "api"、"/api"、"/api/" 的效果一致。
2. 启动后默认会暴露哪些接口
启用默认 controllers 后,会暴露这些路由:
GET /tiles/:layer/:z/:x/:y.pbfGET /tiles-3d/:layer/:z/:x/:y.pbfGET /tiles/:layer/metadataGET /health
如果配置了 routePrefix: "api",上面四类路由会统一变成 /api/...。
3. 怎么发请求
二维示例:
curl --compressed -i \
"http://127.0.0.1:3000/tiles/data/8/209/111.pbf?crs=gcj02&fields=name,color"三维示例:
curl --compressed -i \
"http://127.0.0.1:3000/tiles-3d/data/8/209/111.pbf?crs=gcj02"读取 metadata:
curl "http://127.0.0.1:3000/tiles/data/metadata"读取 health:
curl "http://127.0.0.1:3000/health"HTTP 接口详解
二维接口
GET /tiles/:layer/:z/:x/:y.pbf返回内容:
- 标准二维 MVT
Content-Type: application/vnd.mapbox-vector-tile- 如果开启
gzip.enabled=true,响应头会带Content-Encoding: gzip
适合的前端消费方式:
- MapLibre / Mapbox GL 风格
vector source - 其他能直接消费 MVT 的前端引擎
三维接口
GET /tiles-3d/:layer/:z/:x/:y.pbf返回内容:
- 自定义三维 PBF
Content-Type: application/octet-stream- 如果开启
gzip.enabled=true,响应头会带Content-Encoding: gzip
适合的前端消费方式:
- 自己在前端拿到
ArrayBuffer后解码 - 不能直接当标准 MVT 使用
metadata 接口
GET /tiles/:layer/metadata返回当前图层的配置摘要,常用于:
- 前端初始化前先确认图层信息
- 联调时检查图层名、坐标系、字段白名单
- 运维或健康排查
health 接口
GET /health返回服务和数据库探针状态,常用于:
- 容器健康检查
- 上线后巡检
- 联调时确认数据库是否连通
参数怎么传
路径参数
二维和三维接口都使用同一组路径参数:
| 参数 | 类型 | 是否必填 | 含义 | 示例 |
| --- | --- | --- | --- | --- |
| layer | string | 是 | 图层名,必须命中 layers 配置中的某一项。 | data |
| z | number | 是 | 缩放级别,必须是大于等于 0 的整数。 | 8 |
| x | number | 是 | 当前 z 层级下的瓦片列号。 | 209 |
| y | number | 是 | 当前 z 层级下的瓦片行号。 | 111 |
查询参数
二维和三维接口共用同一组查询参数:
| 参数 | 类型 | 是否必填 | 含义 | 示例 |
| --- | --- | --- | --- | --- |
| crs | string | 否 | 输出坐标系,当前支持 4326 和 gcj02。未传时使用图层的 defaultOutputCrs。 | crs=gcj02 |
| minZoom | number | 否 | 请求级最小缩放限制,会与图层和默认配置一起合并。 | minZoom=6 |
| maxZoom | number | 否 | 请求级最大缩放限制,会与图层和默认配置一起合并。 | maxZoom=16 |
| fields | string | 否 | 逗号分隔的属性字段列表。最终仍受 propertyWhitelist 约束。 | fields=name,color |
| from | string | 否 | 起始时间,要求图层配置了 timeField。 | from=2026-01-01T00:00:00Z |
| to | string | 否 | 结束时间,要求图层配置了 timeField。 | to=2026-12-31T23:59:59Z |
| v | string | 否 | 版本字符串,主要参与缓存键区分。 | v=2026-04-30 |
/tiles/:layer/metadata 只使用路径参数 layer,/health 不接收业务参数。
如果你需要过滤条件,请在服务端内部构造 TileQueryInput.filter 后再调用引擎。默认 HTTP controller 不会从 URL 读取 filter;历史上的 ?filter=... 也会被静默忽略。
filter 现在怎么传
默认公开 HTTP 接口已经移除了 filter 查询参数:
- 不再支持
GET /tiles/...?...&filter=... - 不再支持
GET /tiles-3d/...?...&filter=... - 旧的
?filter=...不会报错,但会被静默忽略
这样做的目的是不再把过滤条件暴露在 URL、访问日志、缓存键观测面和浏览器历史里。
如果你仍然需要过滤能力,请在服务器内部直接传 TileQueryInput.filter,而不是让客户端拼到链接上。
Nest 服务内部传 filter
最直接的方式,是在你自己的 Controller / Service 里调用 TileService.getTile(...) 或 TileService.get3DTile(...),把 filter 放进 TileQueryInput:
import { Controller, Get, Inject, Param, Res } from "@nestjs/common";
import type { TileFilterExpression } from "@deepinnet/service-mvt-middleware-core";
import { TileHttpResponder, TileService } from "@deepinnet/service-mvt-middleware";
const onlyPoint1: TileFilterExpression = {
op: "eq",
field: "name",
value: "point-1",
};
@Controller("internal-tiles")
export class InternalTileController {
constructor(
@Inject(TileService) private readonly tileService: TileService,
@Inject(TileHttpResponder) private readonly tileHttpResponder: TileHttpResponder,
) {}
@Get(":layer/:z/:x/:y.pbf")
async getTile(
@Param("layer") layer: string,
@Param("z") z: string,
@Param("x") x: string,
@Param("y") y: string,
@Res() response?: unknown,
): Promise<void> {
const result = await this.tileService.getTile({
layer,
z: Number(z),
x: Number(x),
y: Number(y),
crs: "4326",
fields: ["name", "color"],
filter: onlyPoint1,
});
this.tileHttpResponder.write(response, result);
}
}filter 能传什么
filter 是 TileQueryInput 的一个内部字段,类型是 TileFilterExpression。当前支持的结构化操作符仍然是:
eqinrangeandor
它仍然会经过底层白名单校验和参数化 SQL 生成;也就是说,filter 只是从公开 URL 上移除了,并没有从服务端内部能力里删除。
返回结果怎么理解
二维
- 成功时通常返回
200 - 无命中或超出缩放范围时通常返回
204 - body 是标准 MVT 二进制
- 若开启
gzip,客户端需要支持 gzip 解压
三维
- 成功时通常返回
200 - 无命中或超出缩放范围时通常返回
204 - body 是自定义 3D PBF 二进制
- 若开启
gzip,客户端需要支持 gzip 解压
常见错误
返回结构一般是:
{
"statusCode": 400,
"code": "INVALID_TILE_QUERY",
"message": "具体错误信息",
"details": {}
}常见错误原因:
z/x/y不是合法瓦片坐标layer不存在crs不在图层允许范围内from/to未配置timeField- 数据库连接失败
TileModule 配置怎么写
TileModule.forRoot(...)
最常用配置项如下:
| 参数 | 类型 | 含义 |
| --- | --- | --- |
| layers | LayerConfigInput[] \| Record<string, LayerConfigInput> | 图层配置集合,定义可访问的图层、几何字段、输出坐标系、cluster、gzip 等能力。 |
| database | PostgresDatabaseOptions | PostgreSQL / PostGIS 连接配置,会用于默认的二维和三维查询执行器。 |
| controllers | false \| TileControllerOptions | 控制器开关,可按需启用二维、三维、metadata 和 health 路由。 |
| routePrefix | string | 给默认 controller 增加统一前缀,例如 "api" 会生成 /api/tiles、/api/health。 |
| defaults | TileEngineDefaults | 模块级默认配置,会在图层未显式声明时作为回退值。 |
| tile | TileRenderOptions | 瓦片渲染参数,例如 extent、buffer、maxFeatures。 |
| healthIndicator | () => Promise<Record<string, unknown>> | 自定义健康检查函数,用于覆盖默认数据库探针。 |
controllers 子项
| 参数 | 类型 | 含义 |
| --- | --- | --- |
| tile | boolean | 是否启用二维接口 /tiles/...。 |
| tile3d | boolean | 是否启用三维接口 /tiles-3d/...。 |
| metadata | boolean | 是否启用 /tiles/:layer/metadata。 |
| health | boolean | 是否启用 /health。 |
defaults 常用子项
| 参数 | 类型 | 含义 |
| --- | --- | --- |
| sourceCrs | string | 图层未显式声明时的默认源坐标系。 |
| defaultOutputCrs | string | 图层未显式声明时的默认输出坐标系。 |
| minZoom | number | 图层未显式声明时的默认最小缩放限制。 |
| maxZoom | number | 图层未显式声明时的默认最大缩放限制。 |
| clusterEnabled | boolean | 图层未显式声明时是否默认开启 cluster。 |
| clusterMaxZoom | number | 图层未显式声明时,cluster 的切换阈值。 |
| clusterCellSize | number | 图层未显式声明时,cluster 网格尺寸。 |
| clusterMaxFeatures | number | 图层未显式声明时,cluster 输出要素上限。 |
| clusterFields | object | 图层未显式声明时,聚合属性字段配置。 |
| clusterAltitudeMode | string | 三维 cluster 的高程聚合规则,当前支持 max。 |
| gzip | object | 响应压缩配置,gzip.enabled=true 时二维和三维接口都会返回 gzip 响应。 |
database 常用子项
| 参数 | 类型 | 含义 |
| --- | --- | --- |
| connectionString | string | PostgreSQL 连接串。 |
| pool | Pool | 已存在的连接池实例。传入后会直接复用,不会由本包自动关闭。 |
| poolConfig | PoolConfig | 细粒度连接池配置。 |
| healthCheckQuery | string | 自定义数据库健康检查 SQL。 |
如果同时传了 pool 和 connectionString / poolConfig,会优先使用 pool。
tile 常用子项
| 参数 | 类型 | 含义 |
| --- | --- | --- |
| extent | number | 瓦片渲染 extent。 |
| buffer | number | 瓦片边缘缓冲区。 |
| maxFeatures | number | 单次查询允许返回的最大要素数。 |
forRootAsync(...) 什么时候用
如果你的数据库连接或图层配置要从 ConfigService、远程配置或其他 provider 动态生成,使用 forRootAsync(...):
TileModule.forRootAsync({
routePrefix: "api",
inject: [ConfigService],
useFactory: async (config: ConfigService) => ({
layers: buildLayers(config),
database: {
connectionString: config.getOrThrow("DATABASE_URL"),
},
}),
})forRootAsync(...) 下如果要配置统一前缀,请放在 TileModule.forRootAsync({...}) 的顶层 routePrefix 字段里,而不是放进 useFactory 返回值中。
默认行为与约束
- 二维接口返回标准 MVT,三维接口返回自定义 3D PBF;参数集合尽量一致,但响应体不能互换。
routePrefix只影响默认 Nest controller 的基础路径,不影响底层core引擎。fields不会绕过白名单,最终只会返回propertyWhitelist允许的字段。filter能力仍存在于底层引擎,但默认 HTTP controller 不会把它暴露成公开查询参数。from/to只有在图层配置了timeField后才可用。minZoom/maxZoom会与图层配置和模块默认值一起计算最终有效范围。clusterEnabled=true时必须配置clusterMaxZoom。clusterFields的输出别名不能与系统保留字段冲突。gzip.enabled=true时响应会带Content-Encoding: gzip。- 三维 cluster 的高度规则当前由
clusterAltitudeMode控制,当前实现为max。 - 若未显式关闭 controller,
TileModule默认会启用二维、三维、metadata、health 四类接口。 - 内部创建的数据库连接池会在应用关闭时自动释放;外部传入的
database.pool需要由调用方自己执行pool.end()。
常见接入方式
给 MapLibre 用二维瓦片
const source = {
type: "vector",
tiles: [
"http://127.0.0.1:3000/tiles/data/{z}/{x}/{y}.pbf?crs=gcj02&fields=name,color"
]
};给前端自定义三维加载器用
import { decodeTile } from "@deepinnet/geojson-to-mvt";
const response = await fetch(
"http://127.0.0.1:3000/tiles-3d/data/8/209/111.pbf?crs=gcj02"
);
const buffer = await response.arrayBuffer();
const tile = decodeTile(new Uint8Array(buffer));主要导出
运行时导出:
TileModuleTileHttpResponderTileServiceTILE_ENGINETILE_MODULE_OPTIONS
类型导出:
TileModuleOptionsTileModuleAsyncOptionsTileControllerOptionsTileControllersSelectionTileOptionsFactory
与 core 的关系
这个包只负责 NestJS 集成层。
所有底层能力都来自:
@deepinnet/service-mvt-middleware-core
包括:
- 图层配置类型
- 瓦片引擎
- PostgreSQL adapter
- filter / cluster / gzip 等能力
协议
本包按 Apache-2.0 协议分发。
