Skip to content

几何类型概述 ​

PostgreSQL 内置了一组几何类型(Geometric Type),可以把点、线、矩形、多边形、圆这类平面图形直接存进数据库,并用操作符做距离计算、包含判断和相交检测。它们适合做简单的位置与区域计算;当你需要坐标系转换、球面距离、复杂空间分析时,则应改用 PostGIS 扩展。

前置知识

阅读本节前,建议先了解:自定义范围类型

基础概念 ​

为什么数据库要内置几何类型 ​

很多业务只需要"二维平面上的位置与形状",例如:

  • 门店选址:判断某个坐标是否落在配送范围内。
  • 仓库布局:记录货架矩形,判断两个货架是否重叠。
  • 游戏/仿真:存储点与路径,计算两点距离。
  • 图像标注:存标注框(box)与多边形轮廓。

这些需求不需要地理坐标系,只需要平面几何计算。PostgreSQL 内置几何类型正好覆盖这一层,零扩展、零依赖。

七种内置几何类型 ​

类型名称存储大小文本表示示例典型用途
point点16 字节(1,2)坐标、位置
line无限直线32 字节{1,-1,0}数学直线(Ax+By+C=0)
lseg线段32 字节[(1,1),(3,3)]有限线段
box矩形32 字节(3,3),(1,1)包围盒、标注框
path路径16+16n 字节[(1,1),(2,2),(3,1)]折线(开放/闭合)
polygon多边形40+16n 字节((1,1),(2,2),(3,1))区域、围栏
circle圆24 字节<(1,2),3>半径范围、服务圈

术语提示

几何类型(Geometric Type)、空间索引(Spatial Index)、坐标参考系统(SRID,Spatial Reference System Identifier)、包围盒(Bounding Box)。PostGIS 相关章节会大量出现 SRID。

点(point) ​

点是所有几何类型中最常用的。它就是一个 (x, y) 坐标对。

sql
SELECT '(1,2)'::point AS p,
       point(3, 4)    AS p2;

-- 结果:
--   p   |  p2
-- -------+-------
--  (1,2) | (3,4)

线(line)与线段(lseg) ​

这两个容易混淆:

  • line 是无限延伸的直线,用三个系数 {A,B,C} 表示方程 Ax + By + C = 0。
  • lseg 是有限线段,由两个端点确定。
sql
-- 无限直线:{A,B,C}
SELECT '{1,-1,0}'::line AS diag;      -- 直线 y = x

-- 有限线段:两个端点
SELECT '[(1,1),(3,3)]'::lseg AS seg;

-- 结果:
--  diag   |    seg
-- --------+-------------
--  {1,-1,0} | [(1,1),(3,3)]

矩形(box) ​

box 用一对对角点表示,与输入顺序无关:PostgreSQL 会自动把它们规范化为"右上角, 左下角"。

sql
SELECT '(1,1),(3,3)'::box AS b1,
       '(3,3),(1,1)'::box AS b2;   -- 顺序颠倒,结果相同

-- 结果:
--    b1     |    b2
-- -----------+-----------
--  (3,3),(1,1) | (3,3),(1,1)

输出格式固定为 (右上角),(左下角),即 (high_x,high_y),(low_x,low_y)。

路径(path)与多边形(polygon) ​

两者都是点序列,区别在闭合性与语义:

sql
-- 路径:方括号,可以是开放折线,也可以首尾点重复表示闭合
SELECT '[(1,1),(2,2),(3,1)]'::path  AS open_path,
       '((1,1),(2,2),(3,1))'::path  AS closed_path;

-- 多边形:圆括号,隐式闭合,不存储重复的首尾点
SELECT '((1,1),(2,2),(3,1))'::polygon AS poly;
特性pathpolygon
定界符[...] 开放 / ((...)) 闭合((...))
是否可自交可以不建议(面积计算会异常)
面积计算仅闭合路径有意义area() 直接支持
典型用途行车轨迹、折线区域围栏、地块

圆(circle) ​

用 <(圆心),半径> 表示:

sql
SELECT '<(1,2),3>'::circle AS c,
       circle(point(1,2), 3) AS c2;

输入格式的容错性 ​

PostgreSQL 的几何类型输入解析非常宽松,以下几种写法等价:

sql
SELECT '(1,2)'::point      AS a,
       '1,2'::point        AS b,     -- 省略外层括号
       ' ( 1 , 2 ) '::point AS c;    -- 允许空白

语法与用法 ​

字面量写法速查 ​

text
-- 点
'(x,y)'
-- 直线(系数 A,B,C)
'{A,B,C}'
-- 线段(两端点)
'[(x1,y1),(x2,y2)]'
-- 矩形(任一对角点,顺序不限)
'((x1,y1),(x2,y2))'
-- 路径(方括号开放、双圆括号闭合)
'[(x1,y1),(x2,y2),...]'
'((x1,y1),(x2,y2),...)'
-- 多边形
'((x1,y1),(x2,y2),...)'
-- 圆
'<(x,y),r>'

构造函数 ​

每种类型都有对应构造函数,便于用表达式动态拼装:

构造函数说明
point(x, y)由两个浮点数构造点
line(point, point)由两点确定直线
lseg(point, point)由两点构造线段
box(point, point)由两对角点构造矩形
path(polygon)由多边形构造闭合路径
polygon(n, circle)由圆构造 n 边正多边形
circle(point, r)由圆心与半径构造圆
sql
-- 用构造函数而非字面量,值可以来自列或变量
SELECT box(point(0, 0), point(10, 5)) AS rect,
       circle(point(5, 2.5), 2)       AS dot;

建表使用几何类型 ​

sql
DROP TABLE IF EXISTS stores;

CREATE TABLE stores (
  id        BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  name      TEXT   NOT NULL,
  location  POINT  NOT NULL,        -- 门店坐标
  coverage  CIRCLE NOT NULL,        -- 配送半径
  bbox      BOX    NOT NULL         -- 库区包围盒
);

INSERT INTO stores (name, location, coverage, bbox) VALUES
  ('城东店', '(116.42,39.92)', '<(116.42,39.92),3>', '(116.45,39.95),(116.39,39.89)'),
  ('城西店', '(116.30,39.92)', '<(116.30,39.92),5>', '(116.35,39.96),(116.25,39.88)');

SELECT id, name, location, coverage FROM stores ORDER BY id;

-- 结果:
--  id |  name  |   location   |     coverage
-- ----+--------+--------------+-------------------
--   1 | 城东店 | (116.42,39.92) | <(116.42,39.92),3>
--   2 | 城西店 | (116.3,39.92)  | <(116.3,39.92),5>

详细说明 ​

几何类型 vs 两个数值列 ​

很多团队用 lat DOUBLE PRECISION, lng DOUBLE PRECISION 存坐标。对比一下:

维度point两个 double precision
距离计算<-> 操作符一行搞定需手写平方和开方
是否在区域内@> / <@ 原生支持需手写几何算法
空间索引原生 GiST / SP-GiST需表达式索引
数据完整性坐标天然成对可能只填一半
输出(x,y) 紧凑两列
可读性需了解语法直观

结论:只存不查时两列更简单;要按位置做查询时 point 明显更优。

距离计算:<-> 操作符 ​

<-> 是所有几何类型通用的距离操作符,返回 double precision:

sql
SELECT '(0,0)'::point <-> '(3,4)'::point AS d_pp,      -- 点到点:5
       '(0,0)'::point <-> '[(0,0),(0,10)]'::lseg AS d_pl; -- 点在线上:0
text
 d_pp | d_pl
------+------
    5 |    0

包含与相交:@>、<@、&& ​

三个最常用的"空间关系"操作符:

操作符含义示例
@>左包含右circle @> point:点在圆内
<@左被右包含point <@ circle:同上
&&包围盒有重叠box && box:两矩形可能相交
sql
-- 点是否在圆内(即是否在配送范围内)
SELECT '<(0,0),5>'::circle @> '(3,4)'::point  AS inside_1,
       '<(0,0),5>'::circle @> '(3,5)'::point  AS inside_2;

-- 结果:
--  inside_1 | inside_2
-- ----------+----------
--  t        | f

&& 只比较包围盒,是快速预筛,不一定代表真实相交,注意别误用。

与 PostGIS 的定位区分 ​

这是初学者最容易踩的坑:内置几何类型不是 GIS 类型。

能力内置几何类型PostGIS geometry / geography
坐标系无,纯平面支持 SRID、投影转换
距离单位坐标单位(抽象)米 / 度,可跨投影计算
球面计算不支持geography 支持
函数丰富度二十余个数百个(ST_*)
格式支持仅自身文本格式WKT / WKB / GeoJSON / KML
安装内置需 CREATE EXTENSION postgis
精度double precision支持更多几何模型(含 Z/M 维度)

选择口诀:

  • 纯平面、小型数据集、简单判断 → 内置几何类型。
  • 经纬度、真实距离(公里)、坐标系、复杂分析 → PostGIS。

更详细的 PostGIS 介绍见后续章节。

索引支持 ​

内置几何类型支持 GiST 与 SP-GiST 索引,对 &&、@>、<@、<->(KNN)等有用。

sql
-- GiST:通用,支持 KNN 距离排序
CREATE INDEX idx_stores_location ON stores USING GIST (location);

-- 按距离排序时会走 GiST 的 KNN 能力
SELECT name, location <-> '(116.40,39.90)'::point AS dist
FROM stores
ORDER BY location <-> '(116.40,39.90)'::point
LIMIT 1;

注意

box 类型在 PostgreSQL 12 之前支持 B-tree 索引,之后已移除;如遇老文档提到"box 可建 btree",属于过时信息。需要按矩形做索引时用 GiST。

常见几何类型的可比较性 ​

并非所有几何类型都支持全部比较操作符:

类型=< > 排序可建 B-tree
point✔(PG 9.4+ 起按坐标比较)✘需 btree_gist 或用表达式
box✔✔(按面积)✘
circle✔✔(按面积)✘
polygon✔✔(按面积)✘
path✔✔(按几何比较)✘
lseg / line✔✔✘

point 的排序缺失是历史遗留:(1,2) 与 (2,1) 谁大谁小在几何上没有自然答案,所以 PostgreSQL 不提供 <。

实战示例 ​

用"门店覆盖范围查询"串起几何类型的常用能力。

第 1 步:准备数据 ​

sql
DROP TABLE IF EXISTS stores;

CREATE TABLE stores (
  id        BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  name      TEXT   NOT NULL,
  location  POINT  NOT NULL,
  coverage  CIRCLE NOT NULL
);

INSERT INTO stores (name, location, coverage) VALUES
  ('城东店', '(10,10)', '<(10,10),3>'),
  ('城西店', '(0,0)',   '<(0,0),5>'),
  ('城南店', '(5,-5)',  '<(5,-5),4>');

SELECT id, name, location, coverage FROM stores ORDER BY id;
text
 id |  name  | location |     coverage
----+--------+----------+-------------------
  1 | 城东店 | (10,10)  | <(10,10),3>
  2 | 城西店 | (0,0)    | <(0,0),5>
  3 | 城南店 | (5,-5)   | <(5,-5),4>

第 2 步:某坐标落在哪些门店的覆盖范围内 ​

sql
SELECT name, '<(0,0),5>'::circle @> location AS wrong_way,   -- 方向反了,恒 false
       coverage @> '(2,2)'::point AS covers
FROM stores
ORDER BY id;
text
  name  | wrong_way | covers
--------+-----------+--------
 城东店 | f         | f
 城西店 | f         | t
 城南店 | f         | f

第一条 <(0,0),5> @> location 判断的是"圆心在原点的圆是否包含门店坐标",而不是"门店的圆是否包含该点",注意操作数方向。

第 3 步:按距离排序找最近门店 ​

sql
SELECT name,
       location <-> '(1,1)'::point AS distance
FROM stores
ORDER BY location <-> '(1,1)'::point
LIMIT 2;
text
  name  | distance
--------+----------
 城西店 | 1.4142135623730951
 城南店 | 7.211102550927978

第 4 步:半径是否覆盖某点(两圆是否相交) ​

sql
-- 两圆相交:圆心距 <= 半径之和
SELECT '<(10,10),3>'::circle && '<(0,0),5>'::circle AS bbox_overlap,
       '<(10,10),3>'::circle <-> '<(0,0),5>'::circle AS center_dist;
text
 bbox_overlap | center_dist
--------------+-------------
 f            | 14.142135623730951

圆心距约 14.14,远大于半径和 8,因此不重叠。

第 5 步:用矩形做包围盒预筛 ​

sql
-- 先用包围盒快速缩小范围,再精确判断
SELECT name FROM stores
WHERE box(location <-> '(-100,-100)'::point, location) && box('(0,0)'::point, '(12,12)'::point)
ORDER BY id;

注意

上面这个 box(location <-> ..., location) 写法是为了演示操作符组合,实际业务中应直接用 location @> ... 或 && 配合 box 列。用表达式构造 box 无法走索引。

第 6 步:清理 ​

sql
DROP TABLE IF EXISTS stores;

注意事项 ​

  1. 内置几何类型没有坐标系概念。 存经纬度后用 <-> 得到的"距离"是度,不是米,且高纬度会严重失真。
  2. && 比较的是包围盒。 返回 true 只代表外框重叠,不代表真实相交,需配合 @> 等精判。
  3. box 输出顺序固定。 始终是 (右上角),(左下角),输入顺序不影响存储。
  4. point 不支持 < 排序。 想按坐标排序需拆成 (p)[0] / (p)[1] 或转成数组。
  5. point 的 = 在 PostgreSQL 9.4 之前是按位比较,之后改为按坐标比较,跨版本迁移时注意。
  6. polygon 自交会导致面积异常。 四边形"蝴蝶形"输入不报错,但 area() 结果是两个三角形之差。
  7. 几何类型没有精度控制。 全部是 double precision,不适合金额类精确计算。
  8. line 很少用于业务。 它表示无限直线,实际需求几乎都是 lseg。
  9. 内置几何类型的函数只有二十余个。 一旦发现需要 ST_* 系列函数,说明该上 PostGIS 了。
  10. 索引是 GiST / SP-GiST,不是 B-tree。 建错索引类型会导致查询走全表扫描。
  11. circle 的比较是按面积。 两个不同形状的几何体可能"相等"(面积相同),这种比较语义在业务中通常没意义。
  12. 几何字面量里不要有中文标点。 中文全角括号会导致解析错误,且报错信息不够直观。

最佳实践 ​

  1. 先明确是否需要地理功能。 涉及经纬度、公里数、坐标系,直接上 PostGIS,别用内置类型硬扛。
  2. point 列名用 location / position 这类语义化名称。 不要叫 p 或 geom,后者与 PostGIS 冲突。
  3. 热查询列一定建 GiST 索引。 尤其是 <-> 距离排序,GiST 的 KNN 能避免全表排序。
  4. 用 && 做预筛、用 @> / <@ 做精判。 这是空间查询的标准两段式写法。
  5. box 适合做包围盒缓存列。 把复杂几何体的外框单独存一列,按外框查询再精判,可显著提速。
  6. 距离计算前后统一单位。 内置类型是抽象坐标单位,务必在文档里写清楚"1 单位 = 1 米"这类约定。
  7. 别用几何类型存真实的经纬度。 度与米不可混算,这是最常见的生产事故来源。
  8. 写测试校验几何语义。 例如断言"点在圆边界上"的 @> 结果,明确边界是包含的。
  9. 数据量超过百万行且需空间分析时迁移到 PostGIS。 迁移成本远低于持续维护自造几何算法。

下一节

继续学习:几何操作符与函数

参考链接 ​