使用 Peewee 自定义字段实现几何数据的存储与查询

在实际开发中,处理地理空间数据(如点、线、面)是一个常见的需求。通常,这些数据以 WKT(Well-Known Text)或 WKB(Well-Known Binary)格式存储在数据库中,并在查询时需要使用 SQL 函数(如 AsTextGeomFromText)进行处理。为了简化开发,我们可以通过 Peewee 的自定义字段功能,将这些逻辑封装到字段中。

本文将分三部分介绍如何实现几何数据的存储与查询:

  1. 直接使用 SQL 查询:手动编写 SQL,易出错且开发效率低。
  2. 使用 Peewee 但不自定义字段:通过 Peewee 的 fn 函数处理几何数据,但仍需手动转换。
  3. 使用 Peewee 自定义字段:封装几何数据的存储与查询逻辑,简化开发。

此外,本文还会介绍如何在 SQLite 中激活空间插件,以支持空间数据类型。


1. 直接使用 SQL 查询

示例代码

假设我们有一个 city 表,其中包含一个 location 字段,用于存储地理空间数据。直接使用 SQL 查询的示例如下:

-- 插入数据
INSERT INTO city (name, location) VALUES ('City A', ST_GeomFromText('POINT(1 2)'));

-- 查询数据
SELECT id, name, ST_AsText(location) AS location FROM city;

缺点

  1. 易出错:手动编写 SQL 容易出错,尤其是在处理复杂查询时。
  2. 开发效率低:每次查询都需要手动编写 SQL 语句,增加了开发工作量。
  3. 维护困难:SQL 语句分散在代码中,难以维护和扩展。

2. 使用 Peewee 但不自定义字段

示例代码

使用 Peewee 时,可以通过 fn 函数处理几何数据,但仍需手动转换。需要注意的是,在使用 SQLite 处理空间数据时,需要先加载空间插件并初始化空间元数据。

from peewee import fn, Model, AutoField, CharField, TextField, SqliteDatabase
import shapely

# 数据库连接
def get_db(path):
    db = SqliteDatabase(path)
    db.load_extension("mod_spatialite")  # 加载空间插件
    db.execute_sql("SELECT InitSpatialMetaData();")  # 初始化空间元数据
    return db

db = get_db(':memory:')

class City(Model):
    id = AutoField()
    name = CharField(max_length=256, default="")
    location = TextField()  # 使用 TextField 存储 WKT 数据
    
    class Meta:
        database = db
        table_name = "city"

# 创建表
db.connect()
db.create_tables([City])

# 插入数据
point = 'POINT(1 2)'
City.create(name="City A", location=fn.GeomFromText(point))

# 查询数据
for city in City.select(City.id, City.name, fn.AsText(City.location).alias('location')):
    location = shapely.from_wkt(city.location)  # 手动转换为 Shapely 对象
    print(city.name, location)

缺点

  1. 代码冗余:每次查询都需要使用 fn.AsTextfn.GeomFromText,增加了代码量。
  2. 手动转换:查询结果需要手动转换为 Shapely 对象,不够方便。
  3. 可读性差:代码中混杂了 SQL 函数和数据处理逻辑,降低了可读性。

3. 使用 Peewee 自定义字段

示例代码

通过自定义字段,我们可以将几何数据的存储与查询逻辑封装到字段中,从而简化开发。

from peewee import fn, Model, AutoField, CharField, TextField, SqliteDatabase
import shapely

# 数据库连接
def get_db(path):
    db = SqliteDatabase(path)
    db.load_extension("mod_spatialite")  # 加载空间插件
    db.execute_sql("SELECT InitSpatialMetaData();")  # 初始化空间元数据
    return db

db = get_db(':memory:')



class GeometryField(TextField):
    
    def __init__(self, geom_type, srid=4326, srtext="", **kwargs):
        self.geom_type = geom_type
        self.srid = srid
        self.srtext = srtext
        super().__init__(**kwargs)
    
    def db_value(self, value):
        # 将 Python 对象转换为 WKT 格式
        txt= shapely.to_wkt(value)
        # 再加上GeomFromText函数
        return fn.GeomFromText(txt, self.srid)
        
    def python_value(self, value):       
   		# 将 WKT 数据转换回 Python 对象
        return shapely.from_wkt(value)
    
    def __sql__(self, ctx):
        print(ctx._sql)
        if ctx._sql[0].startswith("SELECT"):
        	# 查询时使用 AsText
            return ctx.sql(fn.AsText(self.column))
        else:
         	# 其他情况直接使用字段名
            return ctx.sql(self.column)


class City(Model):
    id = AutoField()
    name = CharField(max_length=256, default="")
    location = GeometryField()  # 使用自定义字段
    
    class Meta:
        database = db
        table_name = "city"

# 创建表
db.connect()
db.create_tables([City])

# 插入数据
point = shapely.Point(1, 2)
City.create(name="City A", location=point)

# 查询数据
for city in City.select():
    print(city.name, city.location)  # 输出:City A POINT (1 2)

优点

  1. 代码简洁:将几何数据的处理逻辑封装到字段中,减少了重复代码。
  2. 自动转换:查询结果自动转换为 Shapely 对象,无需手动处理。
  3. 可读性高:代码更加清晰,易于理解和维护。

4. 激活空间插件

在使用 SQLite 处理空间数据时,需要先加载空间插件并初始化空间元数据。以下是实现代码:

def get_db(path):
    db = SqliteDatabase(path)
    db.load_extension("mod_spatialite")  # 加载空间插件
    db.execute_sql("SELECT InitSpatialMetaData();")  # 初始化空间元数据
    return db

在连接数据库时调用 get_db 函数即可:

db = get_db(':memory:')

5. 总结

在处理地理空间数据时,使用 Peewee 自定义字段可以显著提高开发效率和代码质量。以下是三种方法的对比:

方法 优点 缺点
直接使用 SQL 查询 灵活 易出错、开发效率低、维护困难
使用 Peewee 但不自定义字段 减少 SQL 编写,部分自动化 代码冗余、手动转换、可读性差
使用 Peewee 自定义字段 代码简洁、自动转换、可读性高 需要额外实现自定义字段逻辑

推荐在项目中使用自定义字段的方式,尤其是在处理复杂数据类型时,可以显著提升开发效率和代码质量。


6. 参考文档

希望本文对你有所帮助!如果有任何问题,欢迎在评论区留言讨论。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐