MySQL 高级函数 ST_Contains()
:几何包含关系判断的利器
大家好,今天我们要深入探讨 MySQL 中的一个强大的空间函数:ST_Contains()
。这个函数在处理地理空间数据时非常有用,它能帮助我们判断一个几何对象是否完全包含另一个几何对象。 理解并熟练运用 ST_Contains()
,可以解决很多与地理位置相关的实际问题,例如:判断某个区域是否包含某个店铺,或者判断某个城市是否包含某个公园等等。
什么是 ST_Contains()
?
ST_Contains(geom1, geom2)
函数是 MySQL Spatial Extension (也称为 GIS 扩展) 提供的一个函数。 它的作用是:如果几何对象 geom1
完全包含几何对象 geom2
,则返回 1;否则返回 0。
- 包含的定义:
geom1
包含geom2
的严格定义是指geom2
的所有点都位于geom1
内部或边界上,且geom1
的内部与geom2
的内部存在交集。 换句话说,geom2
不能超出geom1
的边界,并且geom1
和geom2
不能完全没有重叠部分。 - 返回值:
ST_Contains()
函数返回一个布尔值,用整数 1 和 0 表示,分别代表 "真" 和 "假"。
ST_Contains()
的语法
其语法非常简单:
ST_Contains(geom1, geom2)
其中:
geom1
: 代表需要判断的包含几何对象。geom2
: 代表需要判断是否被包含的几何对象。
geom1
和 geom2
都必须是有效的几何对象,例如 POINT
, LINESTRING
, POLYGON
等。
如何使用 ST_Contains()
?
要使用 ST_Contains()
函数,首先需要确保你的 MySQL 数据库启用了空间扩展。 通常情况下,MySQL 5.7 及更高版本默认启用该扩展。 如果未启用,你需要手动安装并启用它。
下面我们通过一系列的例子来说明 ST_Contains()
的用法。
1. 创建包含空间数据的表
首先,我们创建两个表,分别存储区域和店铺的地理位置信息。
CREATE TABLE regions (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255),
geom GEOMETRY NOT NULL SRID 4326,
SPATIAL INDEX(geom)
);
CREATE TABLE shops (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255),
geom GEOMETRY NOT NULL SRID 4326,
SPATIAL INDEX(geom)
);
这里:
geom
列存储几何对象,类型为GEOMETRY
。SRID 4326
表示使用的空间参考系统是 WGS 84,这是一个常用的地理坐标系统,用于表示地球表面的经度和纬度。SPATIAL INDEX(geom)
创建空间索引,可以提高空间查询的效率。
2. 插入数据
接下来,我们向表中插入一些数据。
INSERT INTO regions (name, geom) VALUES
('Region A', ST_GeomFromText('POLYGON((10 10, 20 10, 20 20, 10 20, 10 10))', 4326)),
('Region B', ST_GeomFromText('POLYGON((30 30, 40 30, 40 40, 30 40, 30 30))', 4326));
INSERT INTO shops (name, geom) VALUES
('Shop 1', ST_GeomFromText('POINT(15 15)', 4326)),
('Shop 2', ST_GeomFromText('POINT(35 35)', 4326)),
('Shop 3', ST_GeomFromText('POINT(50 50)', 4326));
这里我们插入了两个区域和三个店铺,它们的地理位置分别用 POLYGON
和 POINT
对象表示。 ST_GeomFromText()
函数用于从 WKT (Well-Known Text) 格式的字符串创建几何对象。
3. 使用 ST_Contains()
查询
现在我们可以使用 ST_Contains()
函数来查询哪些店铺位于某个区域内。
SELECT
s.name AS shop_name,
r.name AS region_name
FROM
shops s,
regions r
WHERE
ST_Contains(r.geom, s.geom);
这个查询会返回所有位于某个区域内的店铺的名称和区域名称。 ST_Contains(r.geom, s.geom)
会判断 r.geom
(区域的几何对象) 是否包含 s.geom
(店铺的几何对象)。
查询结果应该如下:
shop_name | region_name |
---|---|
Shop 1 | Region A |
Shop 2 | Region B |
可以看到,Shop 1
位于 Region A
内,Shop 2
位于 Region B
内,而 Shop 3
不在任何区域内,所以没有出现在结果中。
4. 使用子查询优化查询
我们还可以使用子查询来优化查询,例如,找出包含店铺数量最多的区域。
SELECT
r.name AS region_name,
COUNT(s.id) AS shop_count
FROM
regions r
LEFT JOIN
shops s ON ST_Contains(r.geom, s.geom)
GROUP BY
r.name
ORDER BY
shop_count DESC;
这个查询会返回每个区域包含的店铺数量,并按照店铺数量降序排列。 LEFT JOIN
用于连接 regions
和 shops
表,ST_Contains(r.geom, s.geom)
作为连接条件,用于判断店铺是否位于区域内。
5. ST_Contains()
与其他空间函数的结合使用
ST_Contains()
可以与其他空间函数结合使用,以实现更复杂的查询。 例如,我们可以结合 ST_Distance()
函数来查找距离某个区域一定范围内的店铺。
SELECT
s.name AS shop_name,
r.name AS region_name,
ST_Distance(s.geom, r.geom) AS distance
FROM
shops s,
regions r
WHERE
ST_Distance(s.geom, r.geom) < 10 AND NOT ST_Contains(r.geom, s.geom);
这个查询会返回距离某个区域小于 10 个单位,但不在该区域内的店铺的名称、区域名称和距离。 ST_Distance(s.geom, r.geom)
用于计算店铺和区域之间的距离,NOT ST_Contains(r.geom, s.geom)
用于排除位于区域内的店铺。
6. 处理不同类型的几何对象
ST_Contains()
可以处理不同类型的几何对象,例如 POINT
, LINESTRING
, POLYGON
等。 需要注意的是,ST_Contains()
的包含关系是基于几何对象的拓扑关系的,而不是基于它们的面积或长度。
例如,我们可以创建一个包含一条线的区域,然后判断一个点是否在该区域内。
INSERT INTO regions (name, geom) VALUES
('Line Region', ST_GeomFromText('POLYGON((0 0, 10 0, 10 1, 0 1, 0 0))', 4326));
INSERT INTO shops (name, geom) VALUES
('Shop on Line', ST_GeomFromText('POINT(5 0.5)', 4326));
SELECT
s.name AS shop_name,
r.name AS region_name
FROM
shops s,
regions r
WHERE
ST_Contains(r.geom, s.geom) AND r.name = 'Line Region';
在这个例子中,Line Region
是一个包含一条线的矩形区域,Shop on Line
位于该区域内,所以查询会返回 Shop on Line
和 Line Region
。
7. 需要注意的边界情况
在使用 ST_Contains()
时,需要注意一些边界情况:
- 几何对象为空: 如果
geom1
或geom2
为空,则ST_Contains()
返回 NULL。 - 几何对象相等: 如果
geom1
和geom2
相等,则ST_Contains(geom1, geom2)
返回 1。 - 几何对象相交但不包含: 如果
geom1
和geom2
相交,但geom2
不完全位于geom1
内,则ST_Contains(geom1, geom2)
返回 0。 - 几何对象不相交: 如果
geom1
和geom2
不相交,则ST_Contains(geom1, geom2)
返回 0。
为了更好地理解这些边界情况,我们可以通过一些例子来说明。
例子 1:几何对象为空
SELECT ST_Contains(NULL, ST_GeomFromText('POINT(1 1)', 4326)); -- 返回 NULL
SELECT ST_Contains(ST_GeomFromText('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))', 4326), NULL); -- 返回 NULL
例子 2:几何对象相等
SELECT ST_Contains(ST_GeomFromText('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))', 4326), ST_GeomFromText('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))', 4326)); -- 返回 1
例子 3:几何对象相交但不包含
SELECT ST_Contains(ST_GeomFromText('POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))', 4326), ST_GeomFromText('POLYGON((1 1, 3 1, 3 3, 1 3, 1 1))', 4326)); -- 返回 0
例子 4:几何对象不相交
SELECT ST_Contains(ST_GeomFromText('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))', 4326), ST_GeomFromText('POINT(2 2)', 4326)); -- 返回 0
8. ST_Within()
与 ST_Contains()
的区别
ST_Within(geom1, geom2)
函数与 ST_Contains(geom2, geom1)
函数是等价的。 也就是说,ST_Within(geom1, geom2)
判断 geom1
是否完全位于 geom2
内,与 ST_Contains(geom2, geom1)
判断 geom2
是否包含 geom1
的含义相同。
因此,以下两个查询是等价的:
SELECT
s.name AS shop_name,
r.name AS region_name
FROM
shops s,
regions r
WHERE
ST_Contains(r.geom, s.geom);
SELECT
s.name AS shop_name,
r.name AS region_name
FROM
shops s,
regions r
WHERE
ST_Within(s.geom, r.geom);
选择使用哪个函数取决于个人偏好和代码的可读性。
实际应用场景
ST_Contains()
函数在实际应用中有很多用途,例如:
- 地理围栏: 可以使用
ST_Contains()
函数来判断用户是否进入或离开某个地理围栏。 - 区域分析: 可以使用
ST_Contains()
函数来分析某个区域内的店铺数量、人口密度等。 - 位置搜索: 可以使用
ST_Contains()
函数来查找位于某个区域内的所有店铺、餐厅等。 - 游戏开发: 可以使用
ST_Contains()
函数来判断游戏角色是否位于某个区域内,例如安全区、危险区等。 - 物流配送: 可以使用
ST_Contains()
函数来判断配送地址是否位于某个配送区域内。
总而言之,ST_Contains()
是一个非常灵活和强大的空间函数,可以应用于各种与地理位置相关的场景。
代码示例
以下是一个更完整的代码示例,展示了如何使用 ST_Contains()
函数来解决一个实际问题:查找位于某个城市内的所有公园。
-- 创建城市表
CREATE TABLE cities (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255),
geom GEOMETRY NOT NULL SRID 4326,
SPATIAL INDEX(geom)
);
-- 创建公园表
CREATE TABLE parks (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255),
geom GEOMETRY NOT NULL SRID 4326,
SPATIAL INDEX(geom)
);
-- 插入城市数据
INSERT INTO cities (name, geom) VALUES
('New York', ST_GeomFromText('POLYGON((-74.25909 40.47739,-73.70035 40.47739,-73.70035 40.91757,-74.25909 40.91757,-74.25909 40.47739))', 4326));
-- 插入公园数据
INSERT INTO parks (name, geom) VALUES
('Central Park', ST_GeomFromText('POLYGON((-73.97968 40.76987,-73.95719 40.76987,-73.95719 40.79934,-73.97968 40.79934,-73.97968 40.76987))', 4326)),
('Prospect Park', ST_GeomFromText('POLYGON((-73.97145 40.65003,-73.95344 40.65003,-73.95344 40.67374,-73.97145 40.67374,-73.97145 40.65003))', 4326)),
('Outside Park', ST_GeomFromText('POLYGON((-75.0 41.0,-74.0 41.0,-74.0 42.0,-75.0 42.0,-75.0 41.0))', 4326));
-- 查询位于 New York 城市内的所有公园
SELECT
p.name AS park_name,
c.name AS city_name
FROM
parks p,
cities c
WHERE
ST_Contains(c.geom, p.geom) AND c.name = 'New York';
这个查询会返回位于 New York 城市内的所有公园的名称和城市名称。 结果应该如下:
park_name | city_name |
---|---|
Central Park | New York |
Prospect Park | New York |
可以看到,Central Park
和 Prospect Park
位于 New York
城市内,而 Outside Park
不在 New York
城市内,所以没有出现在结果中。
优化 ST_Contains()
查询
在使用 ST_Contains()
处理大量数据时,查询性能可能会受到影响。 以下是一些优化 ST_Contains()
查询的技巧:
- 使用空间索引: 确保你的几何列上创建了空间索引。 空间索引可以显著提高空间查询的效率。
- 限制查询范围: 尽量缩小查询范围,例如使用
WHERE
子句过滤掉不相关的记录。 - 使用 bounding box 过滤: 可以使用
MBRContains()
函数先进行 bounding box 过滤,然后再使用ST_Contains()
函数进行精确过滤。MBRContains()
函数判断一个几何对象的最小边界矩形是否包含另一个几何对象的最小边界矩形,它的效率比ST_Contains()
函数高。 - 避免复杂的几何对象: 尽量使用简单的几何对象,例如
POINT
和POLYGON
。 复杂的几何对象会降低查询效率。 - 使用存储过程或函数: 可以将复杂的空间查询封装到存储过程或函数中,以提高代码的可重用性和可维护性。
总结
ST_Contains()
是 MySQL 中一个强大的空间函数,用于判断一个几何对象是否包含另一个几何对象。 通过本文的讲解,我们了解了 ST_Contains()
的语法、用法、实际应用场景以及优化技巧。 希望这些知识能够帮助你更好地处理地理空间数据,解决实际问题。