MySQL空间数据的使用

本文详细介绍了如何在MySQL中使用空间数据类型(POINT、LINESTRING、POLYGON)来存储地理位置信息。首先展示了相关的建表语句,然后通过具体示例说明了如何插入和查询空间数据。最后还介绍了如何在Spring Boot项目中集成这些功能,包括实体类定义、自定义TypeHandler以及Service层的实现。通过这样的设计,可以方便地进行各种空间关系查询,比如查找多边形内的点、计算距离等。

数据库表设计

MySQL支持多种空间数据类型,这里我们主要用到:

  • POINT:存储点位置
  • LINESTRING:存储线段
  • POLYGON:存储多边形区域

建表语句:

-- 创建点数据表
CREATE TABLE spatial_points (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100),
    location POINT NOT NULL,
    created_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    SPATIAL INDEX(location) 
) ENGINE=InnoDB;

-- 创建线数据表
CREATE TABLE spatial_lines (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100),
    path LINESTRING NOT NULL,
    created_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    SPATIAL INDEX(path)
) ENGINE=InnoDB;

-- 创建面数据表
CREATE TABLE spatial_polygons (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100),
    area POLYGON NOT NULL,
    created_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    SPATIAL INDEX(area)
) ENGINE=InnoDB;

数据操作示例

插入数据

MySQL使用WKT(Well-Known Text)格式来表示空间数据。下面是一些插入数据的例子:

-- 插入一个点(比如上海某个地标的位置)
INSERT INTO spatial_points (name, location) 
VALUES ('Point A', ST_GeomFromText('POINT(121.47 31.23)'));

-- 插入一条线(比如两点之间的路线)
INSERT INTO spatial_lines (name, path)
VALUES ('Line 1', ST_GeomFromText('LINESTRING(121.47 31.23, 121.48 31.24)'));

-- 插入一个多边形(比如某个商圈的范围)
INSERT INTO spatial_polygons (name, area)
VALUES ('Polygon 1', ST_GeomFromText('POLYGON((121.47 31.23, 121.48 31.23, 121.48 31.24, 121.47 31.24, 121.47 31.23))'));

常用空间查询

-- 查找在指定多边形内的所有点
SELECT p.id, p.name
FROM spatial_points p, spatial_polygons poly
WHERE poly.id = 1 
AND ST_Contains(poly.area, p.location);

-- 查找距离某个点1公里以内的所有点
SELECT p2.id, p2.name,
    ST_Distance_Sphere(p1.location, p2.location) as distance
FROM spatial_points p1, spatial_points p2
WHERE p1.id = 1
AND p1.id != p2.id
AND ST_Distance_Sphere(p1.location, p2.location) <= 1000
ORDER BY distance;

-- 计算多边形面积
SELECT id, name, ST_Area(area) * POW(111195, 2) as area_in_sqm
FROM spatial_polygons;

Spring Boot整合

实体类定义

@Data
@TableName("spatial_points")
public class SpatialPoint {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String name;
    
    // 注意:这里用String类型存储WKT格式的点位置
    // 需要配合TypeHandler来处理数据转换
    @TableField(typeHandler = PointTypeHandler.class)
    private String location;
    
    private LocalDateTime createdTime;
}

自定义TypeHandler

为了处理空间数据类型和String之间的转换,我们需要实现一个TypeHandler:

@MappedTypes(String.class)
@MappedJdbcTypes(JdbcType.OTHER)
public class PointTypeHandler extends BaseTypeHandler<String> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) 
            throws SQLException {
        ps.setString(i, String.format("ST_GeomFromText('%s')", parameter));
    }

    @Override
    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return rs.getString(columnName);
    }

    @Override
    public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return rs.getString(columnIndex);
    }

    @Override
    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return cs.getString(columnIndex);
    }
}

Mapper

@Mapper
public interface SpatialPointMapper extends BaseMapper<SpatialPoint> {
}

Service

下面是一些空间关系查询的示例:

public interface ISpatialPointService extends IService<SpatialPoint> {
    List<SpatialPoint> findPointsInPolygon(String polygonWkt);
    // ...
}

@Service
@Transactional
public class SpatialPointServiceImpl extends ServiceImpl<SpatialPointMapper, SpatialPoint> 
    implements ISpatialPointService {

    @Override
    public List<SpatialPoint> getByNameLike(String name) {
        return lambdaQuery()
                .like(SpatialPoint::getName, name)
                .list();
    }
    
    @Override
    public List<SpatialPoint> getByTimeRange(LocalDateTime start, LocalDateTime end) {
        return lambdaQuery()
                .between(SpatialPoint::getCreatedTime, start, end)
                .list();
    }
    
    // 查询在多边形内的所有点
    @Override
    public List<SpatialPoint> findPointsInPolygon(String polygonWkt) {
        return lambdaQuery()
                .apply("ST_Contains(ST_GeomFromText({0}), location)", polygonWkt)
                .list();
    }
    
    @Override
    public List<SpatialPoint> findPointsInPolygon(Long polygonId) {
        // 1. 先通过id获取多边形
        SpatialPolygon polygon = polygonMapper.selectById(polygonId);
        if (polygon == null) {
            return Collections.emptyList();
        }

        // 2. 查找在这个多边形内的所有点
        return lambdaQuery()
                .apply("ST_Contains((SELECT area FROM spatial_polygons WHERE id = {0}), location)", polygonId)
                .list();
    }

    // 或者使用连接查询的方式
    @Override
    public List<SpatialPoint> findPointsInPolygon(Long polygonId) {
        return lambdaQuery()
                .apply("EXISTS (SELECT 1 FROM spatial_polygons p WHERE p.id = {0} AND ST_Contains(p.area, location))", polygonId)
                .list();
    }
}
LICENSED UNDER CC BY-NC-SA 4.0
Comment