PostgREST 集成 PostGIS:geometry 地理数据存取与 application/geo+json 输出实战
【免费下载链接】postgrestREST API for any Postgres database项目地址: https://gitcode.com/GitHub_Trending/po/postgrest
PostgREST 作为直接架设在 PostgreSQL 之上的 REST API,天然支持 PostGIS 扩展的geometry/geography空间数据类型。本篇基于 docs/integrations/postgis.rst 官方集成文档,结合仓库源码与测试用例,完整讲解如何在 PostgREST 中建表存储地理数据、通过标准的application/geo+json媒体类型输出 GeoJSONFeatureCollection、使用生成列扩展要素属性,以及用字符串表示法(EWKT)批量写入多边形数据,并附带兼容旧版 PostGIS 的函数方案。
前置准备:安装 PostGIS 扩展并建立空间表
使用 PostGIS 数据类型的前提是数据库已安装 PostGIS 扩展(官方要求先完成 PostGIS 安装)。在目标数据库中激活模块并建表:
-- 在当前数据库中激活 postgis 模块 create extension if not exists postgis; create table coverage ( id int primary key, name text unique, area geometry ); insert into coverage (id, name, area) values (1, 'small', ST_GeomFromText('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))',4326)), (2, 'big', ST_GeomFromText('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))',4326));area列的类型是 PostGIS 的geometry,建表后 PostgREST 即会自动将其识别为表结构的一部分,无需任何额外配置即可通过 REST 端点访问。
需要注意的是,PostGIS 通常安装在publicschema(或extensionsschema)中。PostgREST 的db-extra-search-path配置(默认值public)正是为了把扩展所在的 schema 加入每个请求的search_path,使扩展中的函数(如ST_AsGeoJSON)可以被数据库对象引用。该参数详见 docs/references/configuration.rst 中的db-extra-search-path小节,可通过PGRST_DB_EXTRA_SEARCH_PATH环境变量或数据库内配置pgrst.db_extra_search_path调整(例如 PostGIS 装在extensionsschema 时设为public, extensions)。
application/geo+json:直接输出 GeoJSON FeatureCollection
PostgREST 内置支持 IANA 标准注册的application/geo+json媒体类型。只要在请求中通过Accept头指定它,端点就会把结果序列化为 RFC 7946 定义的FeatureCollection对象(该能力适用于 PostGIS 3.0.0 及以上版本):
curl "http://localhost:3000/coverage" \ -H "Accept: application/geo+json"响应示例:
{ "type": "FeatureCollection", "features": [ { "type": "Feature", "geometry": { "type": "Polygon", "coordinates": [ [[0,0],[1,0],[1,1],[0,1],[0,0]] ] }, "properties": { "id": 1, "name": "small" } }, { "type": "Feature", "geometry": { "type": "Polygon", "coordinates": [ [[0,0],[10,0],[10,10],[0,10],[0,0]] ] }, "properties": { "id": 2, "name": "big" } } ] }每一行的几何列被放入geometry键,其余普通列(id、name)进入properties键,这正是 GeoJSONFeature的标准结构,可直接被 Leaflet、MapLibre、QGIS 等前端或 GIS 工具消费。
源码级实现原理
application/geo+json是 PostgREST 的内置媒体类型处理器(builtin media type handler)之一,其实现分散在三处源码中:
媒体类型识别:src/library/PostgREST/MediaType.hs 中定义了
MTGeoJSON构造子(第 31 行),decodeMediaType在解析Accept头时匹配("application", "geo+json", _)返回MTGeoJSON(第 141 行),toMime则将其映射为响应头application/geo+json(第 71 行)。聚合 SQL 生成:src/library/PostgREST/Query/SqlFragment.hs 中的
asGeoJsonF是关键的序列化片段:json_build_object('type', 'FeatureCollection', 'features', coalesce(json_agg(ST_AsGeoJSON(_postgrest_t)::json), '[]'))可以看到 PostgREST 正是借助 PostGIS 自带的
ST_AsGeoJSON函数逐行转换几何,再用json_agg聚合成features数组,最后包一层FeatureCollection外壳;coalesce(..., '[]')保证了空结果集返回空数组而非null。处理器注册:src/library/PostgREST/SchemaCache.hs 中把
MTGeoJSON与内置聚合BuiltinOvAggGeoJson绑定,即所有表 / 视图端点在不提供自定义处理器时,默认使用上述asGeoJsonF片段产出 GeoJSON。
测试用例验证的行为细节
仓库中的集成测试 test/spec/Feature/Query/PostGISSpec.hs 对 GeoJSON 输出做了详尽验证,从中可以提炼出几个容易踩坑的行为边界:
- 表必须包含 geometry 列:对没有 geometry 列的表(如测试中的
/projects)请求application/geo+json会返回 400 错误,错误码22023,消息为geometry column is missing; - 空结果返回空 features 数组:
{"type":"FeatureCollection","features":[]},而非报错; ?select必须包含几何列:如?select=id,shop_geom,否则无法确定用哪一列作为geometry;- 多几何列场景:可以通过
?select明确指定使用哪一列作为要素几何(测试中分别以coords与range_area作为 geometry 输出); - RPC 与增删改同样适用:
/rpc/get_shop以及 POST / PATCH / PUT / DELETE 搭配Prefer: return=representation时,均能以application/geo+json返回FeatureCollection; - 响应头:输出时
Content-Type为application/geo+json; charset=utf-8。
此外,在普通application/json输出下,geometry 列会被序列化为带crs信息的 GeoJSON 几何对象(测试第 191-197 行),即默认 JSON 也能查看几何数据,只是不带FeatureCollection包装。
使用生成列附加属性(如面积)
若希望在每个Feature的properties中附带额外字段,比如通过st_area(area)计算多边形面积(以平方单位表示),可以直接给表增加生成列,PostgREST 会把生成列作为普通列输出到properties中:
alter table coverage add square_units double precision generated always as ( st_area(area) ) stored;此后请求application/geo+json,每个Feature的properties中都会包含"square_units": <面积值>。生成列方案的优势是面积值由数据库在写入时自动计算并持久化,查询无需额外计算开销。
兼容旧版 PostGIS:用函数构造 FeatureCollection
application/geo+json媒体类型要求 PostGIS 3.0.0 及以上版本(依赖其ST_AsGeoJSON对整行/聚合的配合)。如果使用更老的 PostGIS 版本,官方文档给出的替代方案是在数据库中编写一个返回json的函数,手工构造FeatureCollection:
create or replace function coverage_geo_collection() returns json as $$ select json_build_object( 'type', 'FeatureCollection', 'features', json_agg( json_build_object( 'type', 'Feature', 'geometry', st_AsGeoJSON(c.area)::json, 'properties', json_build_object('id', c.id, 'name', c.name) ) ) ) from coverage c; $$ language sql;随后通过 RPC 端点调用该函数,结果与内置媒体类型输出完全一致:
curl "http://localhost:3000/rpc/coverage_geo_collection"{ "type": "FeatureCollection", "features": [ { "type": "Feature", "geometry": { "type": "Polygon", "coordinates": [ [[0,0],[1,0],[1,1],[0,1],[0,0]] ] }, "properties": { "id": 1, "name": "small" } }, { "type": "Feature", "geometry": { "type": "Polygon", "coordinates": [ [[0,0],[10,0],[10,10],[0,10],[0,0]] ] }, "properties": { "id": 2, "name": "big" } } ] }这段 SQL 恰好与asGeoJsonF生成的内部片段同构(见 src/library/PostgREST/Query/SqlFragment.hs),可以把它理解为"用 SQL 手工复刻内置处理器",适合在旧版本 PostGIS 或需要完全自定义 GeoJSON 结构(例如按需挑选字段、追加聚合属性)时使用。
字符串表示法写入几何数据(EWKT)
插入多边形数据时,无需在客户端序列化坐标数组,PostgREST 支持以 PostGIS 的**字符串表示法(EWKT)**直接提交几何值——格式为SRID=<srid>;<WKT几何文本>。例如向coverage表批量插入两条记录:
curl "http://localhost:3000/coverage" \ -X POST -H "Content-Type: application/json" \ -d @- << EOF [ { "id": 3, "name": "strip", "area": "SRID=4326;POLYGON((0 0, 50 0, 50 2, 0 2, 0 0))" }, { "id": 4, "name": "diamond", "area": "SRID=4326;POLYGON((5 0, 10 5, 5 10, 0 5, 5 0))" } ] EOFPostgREST 会自动将area字段的字符串强制转换为Polygongeometry 类型并写入。这一机制同样适用于 PATCH / PUT 更新,测试 test/spec/Feature/Query/PostGISSpec.hs 中的 POST 用例即使用"shop_geom": "SRID=4326;POINT(-71.11834 42.373238)"形式写入点数据,并配合Prefer: return=representation返回新建要素的 GeoJSON。
延伸:自定义媒体类型与二进制格式
如果内置的application/geo+json满足不了全部场景,PostgREST 还支持通过域类型(domain)+ 函数 / 聚合自定义媒体类型处理器(详见 docs/references/api/media_type_handlers.rst)。文档中给出的典型案例是 PostGIS 的 TWKB(Tiny WKB)压缩二进制格式:
create extension postgis; create domain "application/vnd.twkb" as bytea; create or replace function get_line (id int) returns "application/vnd.twkb" as $$ select st_astwkb(geom) from lines where id = get_line.id; $$ language sql;之后即可请求Accept: application/vnd.twkb获得压缩二进制输出,PostgREST 会自动把响应头Content-Type设为application/vnd.twkb。这种"以数据库域类型命名媒体类型"的机制,为地理数据的自定义序列化(TWKB、自定义 JSON 结构等)提供了与内置geo+json同等的灵活性。
小结
PostgREST 与 PostGIS 的集成核心可以总结为三点:其一,geometry列无需额外声明即可被 REST 端点暴露,读取、过滤、分页照常工作;其二,Accept: application/geo+json开箱即用地输出符合 RFC 7946 的FeatureCollection(需 PostGIS 3.0.0+),底层由asGeoJsonF调用ST_AsGeoJSON实现,旧版本可用 SQL 函数手工构造等效结构;其三,写入侧支持 EWKT 字符串自动转换,配合生成列还可以在输出中携带st_area等派生属性。结合 test/spec/Feature/Query/PostGISSpec.hs 中的边界行为,开发者可以放心地让地理数据类型走与普通 JSON 完全一致的 REST 通道。
【免费下载链接】postgrestREST API for any Postgres database项目地址: https://gitcode.com/GitHub_Trending/po/postgrest
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考