Rust地理空间数据处理库postgis_diesel的使用:结合PostGIS和Diesel实现高效空间数据库操作

Rust地理空间数据处理库postgis_diesel的使用:结合PostGIS和Diesel实现高效空间数据库操作

PostGIS Diesel是Diesel框架的扩展,用于支持PostGIS类型。它提供了对postgres后端和可选sqlite后端的支持。前者默认启用postgres功能,后者需要启用sqlite功能。

使用示例

确保Geometry类型在作用域中,将postgis_diesel::sql_types::*添加到diesel.toml文件的import_types键中。

假设表定义如下(PostgreSQL):

CREATE EXTENSION IF NOT EXISTS postgis;
CREATE TABLE geometry_samples
(
    id         SERIAL                    PRIMARY KEY,
    point      geometry(Point,4326)      NOT NULL,
    linestring geometry(Linestring,4326) NOT NULL
);

或者SQLite版本:

CREATE TABLE geometry_samples
(
    id         SERIAL                    PRIMARY KEY,
    point      BLOB                      NOT NULL,
    linestring BLOB                      NOT NULL
);

Rust代码(适用于两种后端):

use postgis_diesel::operators::*;
use postgis_diesel::types::*;
use diesel::prelude::*;

#[derive(Insertable)]
#[diesel(table_name = geometry_samples)]
struct NewGeometrySample {
    point: Point,
    linestring: LineString<Point>,
}

#[derive(Queryable)]
struct GeometrySample {
    id: i32,
    point: Point,
    linestring: LineString<Point>,
}

table! {
    use postgis_diesel::sql_types::*;
    use diesel::sql_types::*;
    geometry_samples (id) {
        id -> Int4,
        point -> Geometry,
        linestring -> Geometry,
    }
}

完整示例

以下是一个完整的Rust示例,展示如何使用postgis_diesel进行地理空间数据操作:

use diesel::{insert_into, prelude::*};
use postgis_diesel::{
    operators::*,
    types::{LineString, Point},
};

// 假设已经建立了数据库连接
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let connection = &mut establish_connection();
    
    // 创建新几何样本
    let new_sample = NewGeometrySample {
        point: Point::new(12.34, 56.78),
        linestring: LineString::new(vec![
            Point::new(0.0, 0.0),
            Point::new(1.0, 1.0),
            Point::new(2.0, 2.0),
        ]),
    };

    // 插入数据
    insert_into(geometry_samples::table)
        .values(&new_sample)
        .execute(connection)?;

    // 查询数据
    let results = geometry_samples::table
        .filter(geometry_samples::point.st_distance(&Point::new(12.34, 56.78)).lt(0.1))
        .load::<GeometrySample>(connection)?;

    for sample in results {
        println!(
            "ID: {}, Point: ({}, {}), Linestring length: {}",
            sample.id,
            sample.point.x(),
            sample.point.y(),
            sample.linestring.length()
        );
    }

    Ok(())
}

// 示例表定义
table! {
    use postgis_diesel::sql_types::*;
    use diesel::sql_types::*;
    geometry_samples (id) {
        id -> Int4,
        point -> Geometry,
        linestring -> Geometry,
    }
}

#[derive(Insertable)]
#[diesel(table_name = geometry_samples)]
struct NewGeometrySample {
    point: Point,
    linestring: LineString<Point>,
}

#[derive(Queryable)]
struct GeometrySample {
    id: i32,
    point: Point,
    linestring: LineString<Point>,
}

// 建立数据库连接(示例)
fn establish_connection() -> PgConnection {
    let database_url = "postgres://username:password@localhost/database";
    PgConnection::establish(&database_url)
        .unwrap_or_else(|_| panic!("Error connecting to {}", database_url))
}

如何从Schema中移除自动生成的类型

  1. 生成schema文件: diesel print-schema > src/full_schema.rs
  2. 移除不需要的SQL类型并保存为src/schema.rs
  3. 运行 diff -U6 src/full_schema.rs src/schema.rs > src/schema.patch
  4. 在diesel.toml中添加 patch_file = "src/schema.patch"
  5. 移除src/full_schema.rs,检查diesel print-schema > src/schema.rs不会添加Geometry类型

示例patch文件:

@@ -1,12 +1,9 @@
 // @generated automatically by Diesel CLI.

 pub mod sql_types {
-    #[derive(diesel::query_builder::QueryId, diesel::sql_types::SqlType)]
-    #[diesel(postgres_type(name = "geometry"))]
-    pub struct Geometry;
 
     #[derive(diesel::query_builder::QueryId, diesel::sql_types::SqlType)]
     #[diesel(postgres_type(name = "intensity"))]
     pub struct Intensity;

如何运行测试

  1. 启动Postgis数据库:
docker compose up
  1. 运行测试:
cargo test

安装

在项目目录中运行以下Cargo命令:

cargo add postgis_diesel

或者在Cargo.toml中添加:

postgis_diesel = "3.0.1"

以下是一个增强版的完整示例demo,包含更详细的注释和功能:

// 引入必要的依赖
use diesel::{insert_into, prelude::*};
use postgis_diesel::{
    operators::*,
    types::{LineString, Point},
};
use dotenv::dotenv;
use std::env;

// 主函数
fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 加载.env文件中的环境变量
    dotenv().ok();
    
    // 建立数据库连接
    let connection = &mut establish_connection();
    
    // 创建几何样本数据
    let samples = vec![
        NewGeometrySample {
            point: Point::new(12.34, 56.78),
            linestring: LineString::new(vec![
                Point::new(0.0, 0.0),
                Point::new(1.0, 1.0),
            ]),
        },
        NewGeometrySample {
            point: Point::new(23.45, 67.89),
            linestring: LineString::new(vec![
                Point::new(1.0, 1.0),
                Point::new(2.0, 2.0),
                Point::new(3.0, 3.0),
            ]),
        },
    ];

    // 批量插入数据
    insert_into(geometry_samples::table)
        .values(&samples)
        .execute(connection)?;

    // 查询距离特定点0.5单位范围内的所有几何样本
    let target_point = Point::new(12.34, 56.78);
    let results = geometry_samples::table
        .filter(geometry_samples::point.st_distance(&target_point).lt(0.5))
        .load::<GeometrySample>(connection)?;

    // 打印查询结果
    println!("Found {} nearby geometries:", results.len());
    for (i, sample) in results.iter().enumerate() {
        println!(
            "{}. ID: {}, Point: ({}, {}), Linestring points: {}",
            i + 1,
            sample.id,
            sample.point.x(),
            sample.point.y(),
            sample.linestring.points().len()
        );
    }

    Ok(())
}

// 表结构定义
table! {
    use postgis_diesel::sql_types::*;
    use diesel::sql_types::*;
    geometry_samples (id) {
        id -> Int4,
        point -> Geometry,
        linestring -> Geometry,
    }
}

// 插入数据结构
#[derive(Insertable)]
#[diesel(table_name = geometry_samples)]
struct NewGeometrySample {
    point: Point,
    linestring: LineString<Point>,
}

// 查询结果结构
#[derive(Queryable)]
struct GeometrySample {
    id: i32,
    point: Point,
    linestring: LineString<Point>,
}

// 建立数据库连接
fn establish_connection() -> PgConnection {
    // 从环境变量获取数据库URL
    let database_url = env::var("DATABASE_URL")
        .expect("DATABASE_URL must be set in .env file");
    
    // 建立连接
    PgConnection::establish(&database_url)
        .unwrap_or_else(|_| panic!("Error connecting to {}", database_url))
}

这个增强版示例包含以下改进:

  1. 使用dotenv管理数据库连接配置
  2. 支持批量插入多个几何样本
  3. 更详细的查询结果输出
  4. 更完整的错误处理
  5. 更清晰的结构定义和注释

使用前请确保:

  1. 创建了.env文件并设置DATABASE_URL环境变量
  2. 数据库表结构已按前面的SQL示例创建
  3. 已安装postgis_diesel和相关依赖

1 回复

Rust地理空间数据处理库postgis_diesel的使用:结合PostGIS和Diesel实现高效空间数据库操作

以下是基于您提供的内容整理的完整示例demo:

use diesel::prelude::*;
use postgis_diesel::types::*;
use postgis_diesel::functions::*;

// 定义表结构宏 (通常在schema.rs中)
table! {
    locations (id) {
        id -> Integer,
        name -> Text,
        geom -> Geometry,
    }
}

// 定义模型结构体
#[derive(Queryable, Debug)]
pub struct Location {
    pub id: i32,
    pub name: String,
    pub geom: Point<f64>,
}

#[derive(Insertable)]
#[diesel(table_name = locations)]
pub struct NewLocation {
    pub name: String,
    pub geom: Point<f64>,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 建立数据库连接
    let database_url = "postgres://username:password@localhost/database";
    let mut conn = PgConnection::establish(database_url)?;
    
    // 1. 插入地理位置数据
    let central_park = NewLocation {
        name: "Central Park".to_string(),
        geom: Point::new(-73.968285, 40.785091),  // 经度, 纬度
    };
    
    diesel::insert_into(locations::table)
        .values(&central_park)
        .execute(&mut conn)?;
    
    // 2. 查询所有位置
    println!("所有位置信息:");
    let all_locations = locations::table
        .load::<Location>(&mut conn)?;
    
    for loc in all_locations {
        println!("{}: ({}, {})", loc.name, loc.geom.x(), loc.geom.y());
    }
    
    // 3. 空间查询 - 查找附近1000米内的地点
    let times_square = Point::new(-73.985130, 40.758896);
    let distance = 1000.0;  // 1000米范围内
    
    println!("\nTimes Square附近1000米内的地点:");
    let nearby = locations::table
        .filter(st_d_within(locations::geom, times_square, distance))
        .load::<Location>(&mut conn)?;
    
    for loc in nearby {
        println!("{}: ({}, {})", loc.name, loc.geom.x(), loc.geom.y());
    }
    
    // 4. 计算两个点之间的距离
    let point_a = Point::new(-73.966, 40.78);
    let point_b = Point::new(-73.968, 40.785);
    
    let dist: f64 = select(st_distance(point_a, point_b))
        .get_result(&mut conn)?;
    
    println!("\n两点间距离: {:.2} 米", dist);
    
    // 5. 高级空间操作 - 创建缓冲区
    let empire_state = Point::new(-73.9857, 40.7484);
    let buffered_area = select(st_buffer(empire_state, 300.0))
        .get_result::<Polygon<f64>>(&mut conn)?;
    
    println!("\n帝国大厦300米缓冲区坐标点:");
    for point in buffered_area.exterior() {
        println!("({}, {})", point.x(), point.y());
    }
    
    Ok(())
}

这个完整示例演示了如何使用postgis_diesel库进行以下操作:

  1. 建立与PostgreSQL+PostGIS数据库的连接
  2. 插入包含地理空间数据的记录
  3. 查询并显示所有地理位置数据
  4. 执行空间查询查找附近地点
  5. 计算两个地理点之间的距离
  6. 创建并显示地理点的缓冲区

注意事项:

  1. 需要先创建数据库表结构(如您提供的SQL)
  2. 确保PostGIS扩展已启用
  3. 坐标顺序为经度(X)在前,纬度(Y)在后
  4. 对于生产环境,建议添加空间索引提高查询性能
回到顶部