第08章:几何输出函数

8.1 几何输出函数概述

PostGIS 提供了多种几何输出函数,用于将几何对象转换为各种格式的文本或二进制表示。这些函数对于数据交换、可视化和与其他系统集成至关重要。

8.1.1 输出格式分类

┌─────────────────────────────────────────────────────────────┐
│                    几何输出格式分类                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  文本格式                                                    │
│  ├── WKT (Well-Known Text)         - OGC 标准文本格式       │
│  ├── EWKT (Extended WKT)           - PostGIS 扩展文本格式   │
│  ├── GeoJSON                       - JSON 格式              │
│  ├── GML (Geography Markup)        - XML 格式               │
│  ├── KML (Keyhole Markup)          - Google Earth 格式      │
│  └── SVG (Scalable Vector)         - 可缩放矢量图形         │
│                                                             │
│  二进制格式                                                  │
│  ├── WKB (Well-Known Binary)       - OGC 标准二进制格式     │
│  ├── EWKB (Extended WKB)           - PostGIS 扩展二进制格式 │
│  ├── TWKB (Tiny WKB)               - 紧凑二进制格式         │
│  └── MVT (Mapbox Vector Tile)      - 矢量瓦片格式           │
│                                                             │
│  其他格式                                                    │
│  ├── Encoded Polyline              - Google 编码折线        │
│  ├── GeoHash                       - 空间哈希               │
│  └── LatLonText                    - 经纬度文本             │
│                                                             │
└─────────────────────────────────────────────────────────────┘

8.2 WKT 输出

8.2.1 ST_AsText (WKT)

-- 基本用法
SELECT ST_AsText(ST_MakePoint(116.4, 39.9));
-- POINT(116.4 39.9)

SELECT ST_AsText(ST_MakeLine(ST_MakePoint(0, 0), ST_MakePoint(1, 1)));
-- LINESTRING(0 0,1 1)

SELECT ST_AsText(ST_MakeEnvelope(0, 0, 1, 1));
-- POLYGON((0 0,0 1,1 1,1 0,0 0))

-- 3D 几何
SELECT ST_AsText(ST_MakePoint(116.4, 39.9, 45.5));
-- POINT Z (116.4 39.9 45.5)

-- 带 M 值
SELECT ST_AsText(ST_MakePointM(116.4, 39.9, 100));
-- POINT M (116.4 39.9 100)

-- 4D 几何
SELECT ST_AsText(ST_GeomFromText('POINT ZM(116.4 39.9 45.5 100)'));
-- POINT ZM (116.4 39.9 45.5 100)

-- 控制精度(小数位数)
SELECT ST_AsText(ST_MakePoint(116.40740001, 39.90420001), 4);
-- POINT(116.4074 39.9042)

SELECT ST_AsText(ST_MakePoint(116.40740001, 39.90420001), 2);
-- POINT(116.41 39.9)

-- 多几何类型
SELECT ST_AsText(ST_Collect(
    ST_MakePoint(0, 0),
    ST_MakePoint(1, 1)
));
-- MULTIPOINT((0 0),(1 1))

8.2.2 ST_AsEWKT (Extended WKT)

-- EWKT 包含 SRID 信息
SELECT ST_AsEWKT(ST_SetSRID(ST_MakePoint(116.4, 39.9), 4326));
-- SRID=4326;POINT(116.4 39.9)

SELECT ST_AsEWKT(ST_SetSRID(ST_MakePoint(116.4, 39.9, 45.5), 4326));
-- SRID=4326;POINT Z (116.4 39.9 45.5)

-- 控制精度
SELECT ST_AsEWKT(ST_SetSRID(ST_MakePoint(116.40740001, 39.90420001), 4326), 4);
-- SRID=4326;POINT(116.4074 39.9042)

-- 与 ST_AsText 的区别
SELECT 
    ST_AsText(geom) AS wkt,
    ST_AsEWKT(geom) AS ewkt
FROM poi LIMIT 1;
-- wkt: POINT(116.4 39.9)
-- ewkt: SRID=4326;POINT(116.4 39.9)

8.3 WKB 输出

8.3.1 ST_AsBinary (WKB)

-- 获取 WKB(十六进制显示)
SELECT ST_AsBinary(ST_MakePoint(116.4, 39.9));
-- 返回 bytea 类型

-- 转换为十六进制字符串
SELECT encode(ST_AsBinary(ST_MakePoint(116.4, 39.9)), 'hex');

-- 指定字节序
-- 'NDR': 小端序(默认)
-- 'XDR': 大端序
SELECT encode(ST_AsBinary(ST_MakePoint(0, 0), 'NDR'), 'hex');
SELECT encode(ST_AsBinary(ST_MakePoint(0, 0), 'XDR'), 'hex');

-- WKB 不包含 SRID
SELECT ST_SRID(ST_GeomFromWKB(ST_AsBinary(ST_SetSRID(ST_MakePoint(0, 0), 4326))));
-- 0(SRID 丢失)

8.3.2 ST_AsEWKB (Extended WKB)

-- EWKB 包含 SRID
SELECT encode(ST_AsEWKB(ST_SetSRID(ST_MakePoint(116.4, 39.9), 4326)), 'hex');

-- 保留 SRID
SELECT ST_SRID(ST_GeomFromEWKB(ST_AsEWKB(ST_SetSRID(ST_MakePoint(0, 0), 4326))));
-- 4326

-- 指定字节序
SELECT encode(ST_AsEWKB(ST_SetSRID(ST_MakePoint(0, 0), 4326), 'XDR'), 'hex');

8.3.3 ST_AsHEXEWKB

-- 直接返回十六进制字符串
SELECT ST_AsHEXEWKB(ST_SetSRID(ST_MakePoint(116.4, 39.9), 4326));
-- 0101000020E610000066666666662D5D4066666666E6F34340

-- 等价于
SELECT encode(ST_AsEWKB(ST_SetSRID(ST_MakePoint(116.4, 39.9), 4326)), 'hex');

8.3.4 ST_AsTWKB (Tiny WKB)

-- TWKB 是一种紧凑的二进制格式
-- 适合网络传输和存储

-- 基本用法
SELECT encode(ST_AsTWKB(ST_MakePoint(116.4, 39.9)), 'hex');

-- 控制精度(XY 精度)
SELECT encode(ST_AsTWKB(ST_MakePoint(116.4074, 39.9042), 4), 'hex');  -- 4位小数
SELECT encode(ST_AsTWKB(ST_MakePoint(116.4074, 39.9042), 2), 'hex');  -- 2位小数

-- 完整参数
-- ST_AsTWKB(geom, precision_xy, precision_z, precision_m, include_sizes, include_bboxes)
SELECT encode(ST_AsTWKB(
    ST_SetSRID(ST_MakePoint(116.4074, 39.9042, 45.5), 4326),
    6,    -- XY 精度(6位小数)
    2,    -- Z 精度
    0,    -- M 精度
    true, -- 包含大小信息
    true  -- 包含边界框
), 'hex');

-- 比较不同格式的大小
SELECT 
    pg_column_size(ST_AsBinary(geom)) AS wkb_size,
    pg_column_size(ST_AsEWKB(geom)) AS ewkb_size,
    pg_column_size(ST_AsTWKB(geom, 6)) AS twkb_size
FROM complex_polygons
LIMIT 5;

-- TWKB 聚合
SELECT encode(ST_AsTWKB(
    array_agg(geom),
    ARRAY[id]::bigint[],  -- ID 数组
    6
), 'hex')
FROM poi
WHERE category = '餐饮';

8.4 GeoJSON 输出

8.4.1 ST_AsGeoJSON

-- 基本用法
SELECT ST_AsGeoJSON(ST_MakePoint(116.4, 39.9));
-- {"type":"Point","coordinates":[116.4,39.9]}

SELECT ST_AsGeoJSON(ST_MakeLine(ST_MakePoint(0, 0), ST_MakePoint(1, 1)));
-- {"type":"LineString","coordinates":[[0,0],[1,1]]}

SELECT ST_AsGeoJSON(ST_MakeEnvelope(0, 0, 1, 1));
-- {"type":"Polygon","coordinates":[[[0,0],[0,1],[1,1],[1,0],[0,0]]]}

-- 控制精度
SELECT ST_AsGeoJSON(ST_MakePoint(116.40740001, 39.90420001), 4);
-- {"type":"Point","coordinates":[116.4074,39.9042]}

-- 包含 CRS 信息
SELECT ST_AsGeoJSON(ST_SetSRID(ST_MakePoint(116.4, 39.9), 4326), 15, 2);
-- {"type":"Point","crs":{"type":"name","properties":{"name":"EPSG:4326"}},"coordinates":[116.4,39.9]}

-- 参数说明
-- ST_AsGeoJSON(geom, maxdecimaldigits, options)
-- options 位掩码:
-- 0 = 无选项
-- 1 = 包含边界框
-- 2 = 使用短 CRS(EPSG:xxxx)
-- 4 = 使用长 CRS(urn:ogc:def:crs:EPSG::xxxx)
-- 8 = GeoJSON 规范严格模式

-- 包含边界框
SELECT ST_AsGeoJSON(ST_MakeEnvelope(0, 0, 1, 1), 15, 1);
-- {"type":"Polygon","bbox":[0,0,1,1],"coordinates":[[[0,0],[0,1],[1,1],[1,0],[0,0]]]}

-- 3D 几何
SELECT ST_AsGeoJSON(ST_MakePoint(116.4, 39.9, 45.5));
-- {"type":"Point","coordinates":[116.4,39.9,45.5]}

8.4.2 构建完整的 GeoJSON Feature

-- 创建 GeoJSON Feature
SELECT json_build_object(
    'type', 'Feature',
    'geometry', ST_AsGeoJSON(geom)::json,
    'properties', json_build_object(
        'id', id,
        'name', name,
        'category', category
    )
)
FROM poi
WHERE id = 1;

-- 创建 GeoJSON FeatureCollection
SELECT json_build_object(
    'type', 'FeatureCollection',
    'features', json_agg(
        json_build_object(
            'type', 'Feature',
            'geometry', ST_AsGeoJSON(geom)::json,
            'properties', json_build_object(
                'id', id,
                'name', name,
                'category', category
            )
        )
    )
)
FROM poi
WHERE category = '餐饮';

-- 使用 row_to_json 简化
SELECT json_build_object(
    'type', 'FeatureCollection',
    'features', json_agg(
        json_build_object(
            'type', 'Feature',
            'geometry', ST_AsGeoJSON(geom, 6)::json,
            'properties', to_jsonb(t) - 'geom'
        )
    )
)
FROM (SELECT id, name, category, geom FROM poi) t;

8.4.3 使用 jsonb 函数

-- 使用 jsonb 构建(更高效)
SELECT jsonb_build_object(
    'type', 'FeatureCollection',
    'features', jsonb_agg(
        jsonb_build_object(
            'type', 'Feature',
            'id', id,
            'geometry', ST_AsGeoJSON(geom)::jsonb,
            'properties', jsonb_build_object(
                'name', name,
                'category', category
            )
        )
    )
)
FROM poi
WHERE ST_Intersects(geom, ST_MakeEnvelope(116, 39, 117, 40, 4326));

8.5 GML 和 KML 输出

8.5.1 ST_AsGML

-- 基本 GML 输出
SELECT ST_AsGML(ST_MakePoint(116.4, 39.9));
-- <gml:Point><gml:coordinates>116.4,39.9</gml:coordinates></gml:Point>

-- GML 3 格式
SELECT ST_AsGML(3, ST_MakePoint(116.4, 39.9));
-- <gml:Point><gml:pos>116.4 39.9</gml:pos></gml:Point>

-- 控制精度
SELECT ST_AsGML(3, ST_MakePoint(116.40740001, 39.90420001), 4);

-- 包含 SRID
SELECT ST_AsGML(3, ST_SetSRID(ST_MakePoint(116.4, 39.9), 4326), 15, 1);
-- 添加 srsName 属性

-- 完整参数
-- ST_AsGML(version, geom, maxdecimaldigits, options, nprefix, id)
SELECT ST_AsGML(
    3,                    -- GML 版本
    ST_SetSRID(ST_MakePoint(116.4, 39.9), 4326),
    6,                    -- 精度
    17,                   -- options: 1(srsName) + 16(namespace)
    'custom',             -- 命名空间前缀
    'point1'              -- ID
);

-- options 位掩码:
-- 1 = 输出 srsName
-- 2 = 使用长 SRID URN
-- 4 = GML 3.2 格式
-- 16 = 声明命名空间
-- 32 = LineString 使用 srsDimension

8.5.2 ST_AsKML

-- 基本 KML 输出
SELECT ST_AsKML(ST_MakePoint(116.4, 39.9));
-- <Point><coordinates>116.4,39.9</coordinates></Point>

-- 控制精度
SELECT ST_AsKML(ST_MakePoint(116.40740001, 39.90420001), 4);
-- <Point><coordinates>116.4074,39.9042</coordinates></Point>

-- 3D 几何(KML 使用高程)
SELECT ST_AsKML(ST_MakePoint(116.4, 39.9, 45.5));
-- <Point><coordinates>116.4,39.9,45.5</coordinates></Point>

-- 带名称前缀
SELECT ST_AsKML(ST_MakePoint(116.4, 39.9), 15, 'kml');
-- <kml:Point><kml:coordinates>116.4,39.9</kml:coordinates></kml:Point>

-- 创建完整的 KML 文档
SELECT '<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
  <Document>
    <name>POI 数据</name>' ||
string_agg(
    '<Placemark>
      <name>' || name || '</name>
      <description>' || COALESCE(category, '') || '</description>' ||
      ST_AsKML(geom) ||
    '</Placemark>',
    ''
) ||
  '</Document>
</kml>'
FROM poi
WHERE category = '景点';

8.6 SVG 输出

8.6.1 ST_AsSVG

-- 基本 SVG 路径
SELECT ST_AsSVG(ST_MakePoint(116.4, 39.9));
-- cx="116.4" cy="-39.9"  (注意 Y 轴翻转)

SELECT ST_AsSVG(ST_MakeLine(ST_MakePoint(0, 0), ST_MakePoint(1, 1)));
-- M 0 0 L 1 -1

SELECT ST_AsSVG(ST_MakeEnvelope(0, 0, 1, 1));
-- M 0 0 L 0 -1 1 -1 1 0 Z

-- 控制精度和相对/绝对路径
-- ST_AsSVG(geom, rel, maxdecimaldigits)
-- rel = 0: 绝对路径 (M, L)
-- rel = 1: 相对路径 (m, l)

SELECT ST_AsSVG(ST_MakeEnvelope(0, 0, 1, 1), 0, 2);  -- 绝对路径
SELECT ST_AsSVG(ST_MakeEnvelope(0, 0, 1, 1), 1, 2);  -- 相对路径

-- 创建 SVG 文档
WITH bounds AS (
    SELECT 
        ST_XMin(ST_Collect(geom)) AS min_x,
        ST_YMin(ST_Collect(geom)) AS min_y,
        ST_XMax(ST_Collect(geom)) AS max_x,
        ST_YMax(ST_Collect(geom)) AS max_y
    FROM districts
),
svg_paths AS (
    SELECT id, name, ST_AsSVG(geom, 0, 2) AS path
    FROM districts
)
SELECT 
    '<svg xmlns="http://www.w3.org/2000/svg" 
          viewBox="' || min_x || ' ' || (-max_y) || ' ' || (max_x - min_x) || ' ' || (max_y - min_y) || '">' ||
    string_agg('<path d="' || path || '" fill="steelblue" stroke="white" stroke-width="0.01"/>', '') ||
    '</svg>'
FROM bounds, svg_paths
GROUP BY min_x, min_y, max_x, max_y;

8.7 其他输出格式

8.7.1 ST_AsEncodedPolyline

-- Google 编码折线(用于 Google Maps API)
SELECT ST_AsEncodedPolyline(
    ST_GeomFromText('LINESTRING(-120.2 38.5,-120.95 40.7,-126.453 43.252)', 4326)
);
-- _p~iF~ps|U_ulLnnqC_mqNvxq`@

-- 控制精度(默认 5)
SELECT ST_AsEncodedPolyline(
    ST_GeomFromText('LINESTRING(-120.2 38.5,-120.95 40.7)', 4326),
    6  -- 6 位精度
);

-- 解码
SELECT ST_AsText(ST_LineFromEncodedPolyline('_p~iF~ps|U_ulLnnqC_mqNvxq`@'));

8.7.2 ST_GeoHash

-- 生成 GeoHash
SELECT ST_GeoHash(ST_MakePoint(116.4074, 39.9042));
-- wx4g0ffe3d (默认完整精度)

-- 控制精度
SELECT ST_GeoHash(ST_MakePoint(116.4074, 39.9042), 8);
-- wx4g0ffe

SELECT ST_GeoHash(ST_MakePoint(116.4074, 39.9042), 5);
-- wx4g0

-- 从 GeoHash 创建几何(边界框)
SELECT ST_AsText(ST_GeomFromGeoHash('wx4g0'));
-- POLYGON((116.3671875 39.8583984375, ...))

-- 获取 GeoHash 的点(中心点)
SELECT ST_AsText(ST_PointFromGeoHash('wx4g0'));

-- GeoHash 网格分析
SELECT 
    ST_GeoHash(geom, 5) AS geohash,
    COUNT(*) AS poi_count
FROM poi
GROUP BY ST_GeoHash(geom, 5)
ORDER BY poi_count DESC;

8.7.3 ST_AsLatLonText

-- 度分秒格式
SELECT ST_AsLatLonText(ST_MakePoint(116.4074, 39.9042));
-- 39°54'15.120"N 116°24'26.640"E

-- 自定义格式
SELECT ST_AsLatLonText(ST_MakePoint(116.4074, 39.9042), 'D°M''S.SSS"');
-- 39°54'15.120" 116°24'26.640"

SELECT ST_AsLatLonText(ST_MakePoint(116.4074, 39.9042), 'D.DDD°');
-- 39.904° 116.407°

8.7.4 ST_AsX3D

-- X3D 格式(3D 可视化)
SELECT ST_AsX3D(ST_MakePoint(116.4, 39.9, 45.5));
-- 116.4 39.9 45.5

SELECT ST_AsX3D(ST_GeomFromText('POLYGON Z((0 0 0, 1 0 0, 1 1 0, 0 1 0, 0 0 0))'));

-- 控制精度
SELECT ST_AsX3D(ST_MakePoint(116.40740001, 39.90420001, 45.5), 4);

8.8 MVT 输出

8.8.1 ST_AsMVTGeom

-- 准备 MVT 几何(坐标转换到瓦片坐标系)
SELECT ST_AsMVTGeom(
    geom,
    ST_TileEnvelope(14, 13527, 6214),  -- z/x/y 瓦片边界
    4096,  -- extent(瓦片像素尺寸)
    0,     -- buffer
    true   -- clip to tile
) AS mvt_geom
FROM poi
WHERE geom && ST_TileEnvelope(14, 13527, 6214);

8.8.2 ST_AsMVT

-- 生成 MVT 瓦片
SELECT ST_AsMVT(tile, 'poi') AS mvt
FROM (
    SELECT 
        id,
        name,
        category,
        ST_AsMVTGeom(
            geom,
            ST_TileEnvelope(14, 13527, 6214),
            4096,
            64,
            true
        ) AS geom
    FROM poi
    WHERE geom && ST_TileEnvelope(14, 13527, 6214)
) AS tile;

-- 创建 MVT 瓦片函数
CREATE OR REPLACE FUNCTION get_mvt_tile(z INT, x INT, y INT)
RETURNS bytea AS $$
DECLARE
    tile_bounds geometry;
    mvt bytea;
BEGIN
    tile_bounds := ST_TileEnvelope(z, x, y);
    
    SELECT ST_AsMVT(tile, 'poi', 4096, 'geom') INTO mvt
    FROM (
        SELECT 
            id,
            name,
            category,
            ST_AsMVTGeom(
                geom,
                tile_bounds,
                4096,
                64,
                true
            ) AS geom
        FROM poi
        WHERE geom && tile_bounds
    ) AS tile;
    
    RETURN mvt;
END;
$$ LANGUAGE plpgsql STABLE PARALLEL SAFE;

-- 使用
SELECT get_mvt_tile(14, 13527, 6214);

-- 多图层 MVT
SELECT ST_AsMVT(poi_layer, 'poi') || ST_AsMVT(road_layer, 'roads') AS mvt
FROM (
    SELECT id, name, ST_AsMVTGeom(geom, bounds, 4096, 64, true) AS geom
    FROM poi, (SELECT ST_TileEnvelope(14, 13527, 6214) AS bounds) b
    WHERE geom && bounds
) poi_layer,
(
    SELECT id, name, ST_AsMVTGeom(geom, bounds, 4096, 64, true) AS geom
    FROM roads, (SELECT ST_TileEnvelope(14, 13527, 6214) AS bounds) b
    WHERE geom && bounds
) road_layer;

8.9 格式比较与选择

8.9.1 格式特性对比

格式 人类可读 大小 SRID 3D支持 用途
WKT ✓ 大 ✗ ✓ 调试、日志
EWKT ✓ 大 ✓ ✓ 数据交换
WKB ✗ 中 ✗ ✓ 程序间传输
EWKB ✗ 中 ✓ ✓ PostGIS 内部
TWKB ✗ 小 ✗ ✓ 网络传输
GeoJSON ✓ 大 部分 ✓ Web API
GML ✓ 很大 ✓ ✓ OGC 服务
KML ✓ 大 ✗ ✓ Google Earth
MVT ✗ 小 ✗ ✗ 矢量瓦片

8.9.2 选择建议

-- Web API(推荐 GeoJSON)
SELECT json_build_object(
    'type', 'FeatureCollection',
    'features', json_agg(
        json_build_object(
            'type', 'Feature',
            'geometry', ST_AsGeoJSON(ST_Simplify(geom, 0.0001), 6)::json,
            'properties', json_build_object('id', id, 'name', name)
        )
    )
)
FROM districts;

-- 数据备份(推荐 EWKT 或 EWKB)
COPY (
    SELECT id, name, ST_AsEWKT(geom) AS geom
    FROM spatial_data
) TO '/tmp/backup.csv' WITH CSV HEADER;

-- 移动应用(推荐 TWKB)
SELECT id, name, encode(ST_AsTWKB(geom, 5), 'base64') AS geom
FROM poi
WHERE category = '餐饮';

-- 地图瓦片(推荐 MVT)
SELECT get_mvt_tile(z, x, y);

-- Google Earth(推荐 KML)
SELECT ST_AsKML(geom) FROM poi;

8.10 本章小结

本章详细介绍了 PostGIS 的几何输出函数:

  1. WKT 输出:ST_AsText、ST_AsEWKT
  2. WKB 输出:ST_AsBinary、ST_AsEWKB、ST_AsTWKB
  3. GeoJSON 输出:ST_AsGeoJSON 及 FeatureCollection 构建
  4. GML/KML 输出:ST_AsGML、ST_AsKML
  5. SVG 输出:ST_AsSVG
  6. 其他格式:编码折线、GeoHash、X3D
  7. MVT 输出:矢量瓦片生成

8.11 下一步

在下一章中,我们将学习空间关系函数,包括:

  • 拓扑关系判断
  • 距离关系
  • 方向关系
  • DE-9IM 模型

相关资源: