PostgreSQL +martin将多张表输出成一个 MVT

引言

正常创建表单会造成一个source中只包含一个layers,不易维护。

前提

保证已有martin、PostgreSQL环境,并且pgSQL中已正常创建多表。

一、确认几何字段

在pgAdmin中:

1、选中数据库:要合并的数据库
2、点击顶部Tools
3、打开Qurey Tool
4、执行语句:
sql 复制代码
SELECT
    f_table_schema,
    f_table_name,
    f_geometry_column,
    type,
    srid
FROM geometry_columns
WHERE f_table_name IN ('test_lines', 'test_polygons');

正常看到结果为:

表名 几何字段 类型 SRID
表1 geom LINESTRING 4326
表2 geom POLYGON 4326

下面暂时假设两张表的几何字段都叫 geom。如果查询结果不是 geom,需要把后面 SQL 里的 geom 替换成实际字段名。

二、创建空间索引

执行语句:
sql 复制代码
CREATE INDEX IF NOT EXISTS test_lines_geom_idx
ON public.test_lines
USING GIST (geom);

CREATE INDEX IF NOT EXISTS test_polygons_geom_idx
ON public.test_polygons
USING GIST (geom);

ANALYZE public.test_lines;
ANALYZE public.test_polygons;

空间索引能避免每次请求瓦片都全表扫描。

三、创建组合瓦片函数

执行以下完整SQL:
sql 复制代码
CREATE OR REPLACE FUNCTION public.base_map(
    z integer,
    x integer,
    y integer
)
RETURNS bytea
LANGUAGE sql
STABLE
PARALLEL SAFE
AS $$
WITH
tile_bounds AS (
    SELECT ST_TileEnvelope(z, x, y) AS geom_3857
),

line_layer AS (
    SELECT COALESCE(
        ST_AsMVT(
            line_features,
            'test_lines',
            4096,
            'geom'
        ),
        '\x'::bytea
    ) AS mvt
    FROM (
        SELECT
            ST_AsMVTGeom(
                ST_Transform(t.geom, 3857),
                b.geom_3857,
                4096,
                64,
                true
            ) AS geom
        FROM public.test_lines AS t
        CROSS JOIN tile_bounds AS b
        WHERE t.geom && ST_Transform(b.geom_3857, 4326)
    ) AS line_features
    WHERE geom IS NOT NULL
),

polygon_layer AS (
    SELECT COALESCE(
        ST_AsMVT(
            polygon_features,
            'test_polygons',
            4096,
            'geom'
        ),
        '\x'::bytea
    ) AS mvt
    FROM (
        SELECT
            ST_AsMVTGeom(
                ST_Transform(t.geom, 3857),
                b.geom_3857,
                4096,
                64,
                true
            ) AS geom
        FROM public.test_polygons AS t
        CROSS JOIN tile_bounds AS b
        WHERE t.geom && ST_Transform(b.geom_3857, 4326)
    ) AS polygon_features
    WHERE geom IS NOT NULL
)

SELECT line_layer.mvt || polygon_layer.mvt
FROM line_layer
CROSS JOIN polygon_layer;
$$;

四、添加TileJSON图层说明

继续执行语句:
sql 复制代码
COMMENT ON FUNCTION public.base_map(integer, integer, integer)
IS '{
  "name": "base_map",
  "description": "Lines and polygons composite vector tiles",
  "vector_layers": [
    {
      "id": "test_lines",
      "description": "Line features",
      "fields": {}
    },
    {
      "id": "test_polygons",
      "description": "Polygon features",
      "fields": {}
    }
  ]
}';

这会告诉客户端,base_map 中包含两个 source-layer

五、检查函数

执行语句:
sql 复制代码
SELECT
    routine_schema,
    routine_name,
    data_type
FROM information_schema.routines
WHERE routine_name = 'base_map';

应该能看到:

| routine_schema name | routine_name name | data_type character varying |

public base_map bytea

六、重启martin

1、访问(默认配置):http://localhost:3000/catalog
2、访问(主要):http://localhost:3000/base_map

正常应返回:

javascript 复制代码
{
  "name": "base_map",
  "tiles": [
    "http://localhost:3000/base_map/{z}/{x}/{y}"
  ],
  "vector_layers": [
    {
      "id": "test_lines"
    },
    {
      "id": "test_polygons"
    }
  ]
}

七、配置前端source

仅供参考
javascript 复制代码
{
  "version": 8,
  "name": "MyLocalStyle",
  "metadata": {
    "maputnik:renderer": "mlgljs"
  },
  "sources": {
    "base_map": {
      "type": "vector",
      "url": "http://localhost:3000/base_map"
    }
  },
  "layers": [
    {
      "id": "background",
      "type": "background",
      "paint": {
        "background-color": "#f8f8f8"
      }
    },
    {
      "id": "test_polygons_fill",
      "type": "fill",
      "source": "base_map",
      "source-layer": "test_polygons",
      "paint": {
        "fill-color": "rgba(255, 255, 13, 1)",
        "fill-opacity": 0.1
      }
    },
    {
      "id": "test_polygons_outline",
      "type": "line",
      "source": "base_map",
      "source-layer": "test_polygons",
      "paint": {
        "line-width": 2,
        "line-color": "#4aa3ff"
      }
    },
    {
      "id": "test_lines_layer",
      "type": "line",
      "source": "base_map",
      "source-layer": "test_lines",
      "paint": {
        "line-width": 3,
        "line-color": "#ff0000"
      }
    }
  ],
  "id": "oyb34yv"
}

八、如有新建表如何添加到集合

新建表后,base_map 不会自动包含它。需要修改 public.base_map() 函数,把新表生成的 MVT 拼进去,然后更新函数注释并重启 Martin。

假设新表叫:test_points,几何字段是:geom,坐标系是 EPSG:4326。

1、确认新表信息

gisdb → Query Tool 执行:

sql 复制代码
SELECT
    f_table_name,
    f_geometry_column,
    type,
    srid
FROM geometry_columns
WHERE f_table_name = 'test_points';

确认:几何字段为 geom,类型为 POINTMULTIPOINT,SRID 为 4326

2、创建空间索引
sql 复制代码
CREATE INDEX IF NOT EXISTS test_points_geom_idx
ON public.test_points
USING GIST (geom);

ANALYZE public.test_points;
3、更新 base_map 函数

用下面的完整 SQL覆盖原来的函数:

sql 复制代码
CREATE OR REPLACE FUNCTION public.base_map(
    z integer,
    x integer,
    y integer
)
RETURNS bytea
LANGUAGE sql
STABLE
PARALLEL SAFE
AS $$
WITH
tile_bounds AS (
    SELECT ST_TileEnvelope(z, x, y) AS geom_3857
),

line_layer AS (
    SELECT COALESCE(
        ST_AsMVT(
            line_features,
            'test_lines',
            4096,
            'geom'
        ),
        '\x'::bytea
    ) AS mvt
    FROM (
        SELECT
            ST_AsMVTGeom(
                ST_Transform(t.geom, 3857),
                b.geom_3857,
                4096,
                64,
                true
            ) AS geom
        FROM public.test_lines AS t
        CROSS JOIN tile_bounds AS b
        WHERE t.geom && ST_Transform(b.geom_3857, 4326)
    ) AS line_features
    WHERE geom IS NOT NULL
),

polygon_layer AS (
    SELECT COALESCE(
        ST_AsMVT(
            polygon_features,
            'test_polygons',
            4096,
            'geom'
        ),
        '\x'::bytea
    ) AS mvt
    FROM (
        SELECT
            ST_AsMVTGeom(
                ST_Transform(t.geom, 3857),
                b.geom_3857,
                4096,
                64,
                true
            ) AS geom
        FROM public.test_polygons AS t
        CROSS JOIN tile_bounds AS b
        WHERE t.geom && ST_Transform(b.geom_3857, 4326)
    ) AS polygon_features
    WHERE geom IS NOT NULL
),

point_layer AS (
    SELECT COALESCE(
        ST_AsMVT(
            point_features,
            'test_points',
            4096,
            'geom'
        ),
        '\x'::bytea
    ) AS mvt
    FROM (
        SELECT
            ST_AsMVTGeom(
                ST_Transform(t.geom, 3857),
                b.geom_3857,
                4096,
                64,
                true
            ) AS geom
        FROM public.test_points AS t
        CROSS JOIN tile_bounds AS b
        WHERE t.geom && ST_Transform(b.geom_3857, 4326)
    ) AS point_features
    WHERE geom IS NOT NULL
)

SELECT
    line_layer.mvt
    || polygon_layer.mvt
    || point_layer.mvt
FROM line_layer
CROSS JOIN polygon_layer
CROSS JOIN point_layer;
$$;

新增内容主要是:point_layer AS (...),以及最后增加的 || point_layer.mvt

4、更新图层说明

sql 复制代码
COMMENT ON FUNCTION public.base_map(integer, integer, integer)
IS '{
  "name": "base_map",
  "description": "Composite vector tiles",
  "vector_layers": [
    {
      "id": "test_lines",
      "description": "Line features",
      "fields": {}
    },
    {
      "id": "test_polygons",
      "description": "Polygon features",
      "fields": {}
    },
    {
      "id": "test_points",
      "description": "Point features",
      "fields": {}
    }
  ]
}';

5、测试函数

sql 复制代码
SELECT octet_length(public.base_map(0, 0, 0)) AS tile_size;

不报错就说明 SQL 基本正确。

6、重启martin,前端更新配置文件

注:以上就是完整创建组合矢量瓦片函数的方法,如有建议请大胆提出!

相关推荐
aax12134531 小时前
VOC 集群治理工程实操|美丽蓝天绿岛项目 RTO 工艺选型、管控方案与申报要
大数据·数据库·人工智能
mayaairi1 小时前
JS循环语句深度解析:嵌套for、while与do...while
开发语言·前端·javascript
To_OC2 小时前
啃完流式输出:从一个卡顿的 LLM 接口开始,我搞懂了数据流到底怎么 “流”
前端·javascript·llm
IvorySQL2 小时前
PG 日报|优化缓冲区批量扫描,降低多套接字并发竞争
数据库·人工智能·postgresql·开源·区块链
阳光是sunny2 小时前
LangGraph实战教程:defer延迟节点——让收尾工作自动排到最后
前端·人工智能·后端
这个DBA有点耶2 小时前
交易型数据库是什么?OLTP核心能力与2026选型指南
数据库·数据仓库·sql·database·数据库架构·olap·dba
kyriewen2 小时前
我用了三周Claude Code Skills——总结出5条铁律,第3条最反直觉
前端·ai编程·claude
大熊猫侯佩2 小时前
WWDC26 全新 SwiftData 的 .codable 宏选项:它不是泛型专用胶水
数据库·swift·apple
阳光是sunny2 小时前
LangGraph实战教程:控制流详解
前端·人工智能·后端