@deepinnet/service-mvt-middleware-core
v0.0.1
Published
Core tile engine, shared types, and PostgreSQL adapters for DeepInNet vector tile services.
Downloads
147
Readme
@deepinnet/service-mvt-middleware-core
底层核心包,提供:
- 图层配置类型
- 二维 / 三维瓦片引擎
- PostgreSQL / PostGIS adapter
- filter / cluster / gzip 等核心能力
这个包不负责 HTTP 路由。如果你想要开箱即用的 NestJS 路由层,请使用:
@deepinnet/service-mvt-middleware
如果你要自己接 Express、Fastify、Koa、原生 Node HTTP、Serverless handler,或者只想在服务内部直接拿到瓦片二进制结果,这个包就是直接使用层。
安装
pnpm add @deepinnet/service-mvt-middleware-core这个包怎么接入
最常见的接入方式分两种:
直接用内置 PostgreSQL adapter
你只提供图层配置和数据库连接,createTileEngine(...)自动完成二维 / 三维查询。自己实现查询执行器
你把queryExecutor/query3DExecutor接到自己的数据层,这样可以脱离 PostgreSQL adapter。
方式一:直接用内置 PostgreSQL adapter
import { createTileEngine } from "@deepinnet/service-mvt-middleware-core";
const engine = createTileEngine({
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,
},
});方式二:自己提供二维 / 三维查询执行器
import { createTileEngine } from "@deepinnet/service-mvt-middleware-core";
const engine = createTileEngine({
layers: [
{
layer: "data",
sourceType: "table",
sourceObject: "public.geom",
idField: "id",
geomField: "geometry",
},
],
queryExecutor: async (context) => {
// 这里由你自己返回二维 MVT 二进制
return {
body: new Uint8Array(),
contentType: "application/vnd.mapbox-vector-tile",
};
},
query3DExecutor: async (context) => {
// 这里由你自己返回三维自定义 PBF 二进制
return {
body: new Uint8Array(),
contentType: "application/octet-stream",
};
},
});如何请求二维 / 三维瓦片
这个包本身不处理 HTTP,但你最终通常会把外部请求参数映射到:
engine.queryTile(input)
返回二维 MVTengine.query3DTile(input)
返回三维自定义 PBF
最小调用示例
二维:
const result = await engine.queryTile({
layer: "data",
z: 8,
x: 209,
y: 111,
crs: "gcj02",
fields: ["name", "color"],
});三维:
const result = await engine.query3DTile({
layer: "data",
z: 8,
x: 209,
y: 111,
crs: "gcj02",
});返回结果 TileQueryResult 中最常用的字段:
| 字段 | 含义 |
| --- | --- |
| status | HTTP 语义状态码,通常是 200 或 204。 |
| headers | 额外响应头,通常是缓存或扩展头。 |
| body | 瓦片二进制内容。 |
| contentType | 二维通常是 application/vnd.mapbox-vector-tile,三维通常是 application/octet-stream。 |
| isEmpty | 是否为空瓦片。 |
| compression | 压缩标志,例如 { gzip: true }。是否真正写出 Content-Encoding 取决于你的 HTTP 层。 |
如果你要自己接 HTTP
最常见的模式,是把 URL 和 query 参数手动映射成 TileQueryInput。
Express 示例
import express from "express";
import { createTileEngine } from "@deepinnet/service-mvt-middleware-core";
const app = express();
const engine = createTileEngine({
layers: [
{
layer: "data",
sourceType: "table",
sourceObject: "public.geom",
idField: "id",
geomField: "geometry",
geom3dField: "geometry3d",
},
],
database: {
connectionString: process.env.DATABASE_URL,
},
});
function parseFields(value?: string): string[] | undefined {
if (!value) {
return undefined;
}
return value
.split(",")
.map((field) => field.trim())
.filter(Boolean);
}
app.get("/tiles/:layer/:z/:x/:y.pbf", async (req, res) => {
const result = await engine.queryTile({
layer: req.params.layer,
z: Number(req.params.z),
x: Number(req.params.x),
y: Number(req.params.y),
crs: req.query.crs as "4326" | "gcj02" | undefined,
minZoom: req.query.minZoom ? Number(req.query.minZoom) : undefined,
maxZoom: req.query.maxZoom ? Number(req.query.maxZoom) : undefined,
fields: parseFields(req.query.fields as string | undefined),
from: req.query.from as string | undefined,
to: req.query.to as string | undefined,
version: req.query.v as string | undefined,
});
if (result.contentType) {
res.setHeader("Content-Type", result.contentType);
}
for (const [key, value] of Object.entries(result.headers)) {
res.setHeader(key, value);
}
if (result.compression?.gzip) {
res.setHeader("Content-Encoding", "gzip");
}
res.status(result.status).send(Buffer.from(result.body));
});TileQueryInput.filter 仍然可用,但建议由服务端内部逻辑填充。默认的 Nest middleware 不会把它暴露成公开 query 参数;如果你自己接 HTTP,请按自己的安全边界决定是否注入该字段。
参数怎么传
TileQueryInput
queryTile(...) 和 query3DTile(...) 使用同一组查询输入。
| 参数 | 类型 | 是否必填 | 含义 | 示例 |
| --- | --- | --- | --- | --- |
| layer | string | 是 | 图层名,必须命中 layers 配置中的某一项。 | "data" |
| z | number | 是 | 缩放级别,必须是大于等于 0 的整数。 | 8 |
| x | number | 是 | 当前 z 层级下的瓦片列号。 | 209 |
| y | number | 是 | 当前 z 层级下的瓦片行号。 | 111 |
| crs | "4326" \| "gcj02" | 否 | 输出坐标系。未传时使用图层的 defaultOutputCrs。 | "gcj02" |
| minZoom | number | 否 | 请求级最小缩放限制,会与图层和默认配置一起合并。 | 6 |
| maxZoom | number | 否 | 请求级最大缩放限制,会与图层和默认配置一起合并。 | 16 |
| fields | string[] | 否 | 希望返回的属性字段数组,最终仍受 propertyWhitelist 限制。 | ["name", "color"] |
| filter | TileFilterExpression | 否 | 结构化过滤条件,适合由服务端内部逻辑注入。 | { op: "eq", field: "name", value: "point-1" } |
| from | string | 否 | 起始时间,要求图层配置了 timeField。 | "2026-01-01T00:00:00Z" |
| to | string | 否 | 结束时间,要求图层配置了 timeField。 | "2026-12-31T23:59:59Z" |
| version | string | 否 | 版本字符串,主要参与缓存键区分。 | "2026-04-30" |
filter 怎么写
当前支持的结构化过滤操作符:
eqinrangeandor
eq
{
op: "eq",
field: "name",
value: "point-1",
}in
{
op: "in",
field: "color",
values: ["#ff0000", "#00ff00"],
}range
{
op: "range",
field: "created_at",
min: "2026-01-01T00:00:00Z",
max: "2026-12-31T23:59:59Z",
}and
{
op: "and",
filters: [
{ op: "eq", field: "name", value: "point-1" },
{ op: "in", field: "color", values: ["#ff0000", "#00ff00"] },
],
}or
{
op: "or",
filters: [
{ op: "eq", field: "name", value: "point-1" },
{ op: "eq", field: "name", value: "point-2" },
],
}该怎么配置图层
LayerConfigInput
图层配置决定某个 layer 的数据来源和行为。
| 参数 | 类型 | 是否必填 | 含义 |
| --- | --- | --- | --- |
| layer | string | 是 | 图层名,也是查询时传入的 layer 标识。 |
| sourceType | "table" \| "view" \| "sql" | 是 | 数据源类型。当前 PostgreSQL adapter 只支持 table 和 view。 |
| sourceObject | string | 是 | 源表名或视图名,例如 public.geom。 |
| idField | string | 是 | 主键字段,用于返回要素 id。 |
| geomField | string | 条件必填 | 二维几何字段。与 wktField 至少提供一个。 |
| geom3dField | string | 否 | 三维几何字段。三维查询优先使用它。 |
| wktField | string | 条件必填 | WKT 文本字段,可作为 geomField 的替代输入。 |
| timeField | string | 否 | 时间字段,启用 from/to 过滤时必须提供。 |
| sourceCrs | "4326" \| "gcj02" | 否 | 源数据坐标系。 |
| defaultOutputCrs | "4326" \| "gcj02" | 否 | 请求未显式传 crs 时的默认输出坐标系。 |
| supportedOutputCrs | OutputCrs[] | 否 | 当前图层允许输出的坐标系集合。 |
| propertyWhitelist | string[] | 否 | 允许暴露给客户端的属性字段白名单。 |
| minZoom | number | 否 | 图层级最小缩放限制。 |
| maxZoom | number | 否 | 图层级最大缩放限制。 |
| clusterEnabled | boolean | 否 | 是否启用 cluster。 |
| clusterMaxZoom | number | 条件必填 | 当 clusterEnabled=true 时必须配置,表示 z <= clusterMaxZoom 时走聚合路径。 |
| clusterCellSize | number | 否 | cluster 网格尺寸,数值越大聚合越粗。 |
| clusterMaxFeatures | number | 否 | cluster 结果最多输出多少条聚合要素。 |
| clusterFields | Record<string, TileClusterFieldConfig> | 否 | 聚合属性字段配置。 |
| clusterAltitudeMode | "max" | 否 | 三维 cluster 高程聚合规则,当前只支持 max。 |
| gzip | { enabled?: boolean } | 否 | 响应压缩配置,gzip.enabled=true 时结果会标记为 gzip。 |
clusterFields 要怎么写
clusterFields 的 key 是聚合后输出的属性名,value 用来指定源字段和聚合算子。
clusterFields: {
color: {
field: "color",
op: "top",
},
maxHeight: {
field: "height",
op: "max",
},
}当前支持的聚合算子:
summinmaxfirsttop
约束:
- 输出别名不能与系统保留字段冲突,如
id、count、isCluster、clusterId - 源字段必须出现在
propertyWhitelist中
默认配置怎么传
TileEngineDefaults
defaults 用来提供图层缺省值。
| 参数 | 类型 | 含义 |
| --- | --- | --- |
| sourceCrs | "4326" \| "gcj02" | 图层未显式声明时的默认源坐标系。 |
| defaultOutputCrs | "4326" \| "gcj02" | 图层未显式声明时的默认输出坐标系。 |
| minZoom | number | 图层未显式声明时的默认最小缩放限制。 |
| maxZoom | number | 图层未显式声明时的默认最大缩放限制。 |
| clusterEnabled | boolean | 图层未显式声明时是否默认启用 cluster。 |
| clusterMaxZoom | number | 图层未显式声明时的 cluster 阈值。 |
| clusterCellSize | number | 图层未显式声明时的 cluster 网格尺寸。 |
| clusterMaxFeatures | number | 图层未显式声明时的 cluster 输出上限。 |
| clusterFields | object | 图层未显式声明时的聚合属性配置。 |
| clusterAltitudeMode | "max" | 图层未显式声明时的三维聚合高程规则。 |
| gzip | { enabled?: boolean } | 图层未显式声明时的响应压缩配置。 |
createTileEngine(...) 还能接哪些参数
TileEngineOptions
除了 layers 和 defaults,还支持这些选项:
| 参数 | 类型 | 含义 |
| --- | --- | --- |
| database | PostgresDatabaseOptions | PostgreSQL / PostGIS 连接配置。 |
| tile | TileRenderOptions | 瓦片渲染参数,例如 extent、buffer、maxFeatures。 |
| queryExecutor | TileQueryExecutor | 自定义二维查询执行器,会覆盖默认 PostgreSQL adapter。 |
| query3DExecutor | TileQueryExecutor | 自定义三维查询执行器,会覆盖默认 PostgreSQL adapter。 |
| metadataResolver | TileMetadataResolver | 自定义 metadata 扩展逻辑。 |
| cacheKeyVersion | string | 缓存键版本号,用于主动打断旧缓存。 |
PostgresDatabaseOptions
| 参数 | 类型 | 含义 |
| --- | --- | --- |
| pool | Pool | 已存在的 pg.Pool 实例。 |
| connectionString | string | 连接字符串,例如 postgresql://user:pass@host:5432/db。 |
| poolConfig | PoolConfig | pg 的细粒度连接池配置。 |
| healthCheckQuery | string | 健康检查 SQL,默认是 SELECT 1。 |
TileRenderOptions
| 参数 | 类型 | 含义 |
| --- | --- | --- |
| extent | number | MVT 渲染 extent。 |
| buffer | number | 瓦片边缘缓冲区。 |
| maxFeatures | number | 单次查询允许返回的最大要素数。 |
常见接入模式
只做二维 MVT 服务
- 配
geomField - 调
queryTile(...) - 不一定需要
geom3dField
同时做二维和三维
- 同时配
geomField和geom3dField - 二维走
queryTile(...) - 三维走
query3DTile(...)
只做三维
- 推荐配
geom3dField - 如果没有
geom3dField,但有geomField/wktField,三维链路会尝试回退并补z=0
默认行为与约束
crs当前仅支持4326和gcj02。fields最终受propertyWhitelist限制。filter不是任意 SQL 字符串,而是结构化 JSON 条件。from/to要求图层配置了timeField。minZoom/maxZoom会和图层配置、默认配置一起合并。clusterEnabled=true时必须提供clusterMaxZoom。- 三维
clusterAltitudeMode当前仅支持max。 gzip.enabled=true时结果会带 gzip 压缩标志,是否真正写成 HTTPContent-Encoding取决于你的 HTTP 层。- PostgreSQL adapter 当前只支持
table和view,不支持sql数据源。 - 如果
supportedOutputCrs配了值,请求里的crs必须命中该集合。
主要导出
主入口:
createTileEnginebuildCacheKeyloadLayerConfigTileErrorisTileError
PostgreSQL 入口:
createPostgresTileExecutorcreatePostgres3DTileExecutorcreatePostgresPoolcreatePostgresHealthIndicatorDEFAULT_POSTGRES_HEALTH_QUERYDEFAULT_TILE_RENDER_OPTIONS
主要能力
- 二维标准 MVT 查询
- 三维自定义 PBF 查询
- JSON filter 表达式
- 二维 / 三维 gzip 压缩标志
- 二维 / 三维 cluster
- PostgreSQL / PostGIS 查询适配
- 4326 / gcj02 输出转换
导入示例
import { createTileEngine } from "@deepinnet/service-mvt-middleware-core";
import { createPostgresTileExecutor } from "@deepinnet/service-mvt-middleware-core/postgres";协议
本包按 Apache-2.0 协议分发。
