ORM教程

(本教程的灵感来自 SQLAlchemy ORM Tutorial ,最终推荐阅读。)

GeoAlchemy不提供对象关系映射器(ORM),但可以很好地与SQLAlchemy ORM配合使用。本教程展示了如何将SQLAlchemy ORM与空间表一起使用,并使用GeoAlchemy。

连接到数据库

在本教程中,我们将使用PostGIS 2数据库。为了连接我们使用SQLAlchemy的 create_engine() 功能:

>>> from sqlalchemy import create_engine
>>> engine = create_engine('postgresql://gis:gis@localhost/gis', echo=True)

在本例中,数据库的名称、数据库用户和数据库密码是 gis .

这个 echo flag是设置SQLAlchemy日志的快捷方式,它是通过Python的标准日志模块实现的。启用后,我们将看到生成的所有SQL。

的返回值 create_engine 是一个 Engine 对象,它表示数据库的核心接口。

声明映射

当使用ORM时,配置过程首先描述我们要处理的数据库表,然后定义我们自己的类,这些类将映射到这些表。在现代的SQLAlchemy中,这两个任务通常一起执行,使用一个称为 Declarative ,这允许我们创建包含指令的类来描述它们将映射到的实际数据库表。

>>> from sqlalchemy.ext.declarative import declarative_base
>>> from sqlalchemy import Column, Integer, String
>>> from geoalchemy2 import Geometry
>>>
>>> Base = declarative_base()
>>>
>>> class Lake(Base):
...     __tablename__ = 'lake'
...     id = Column(Integer, primary_key=True)
...     name = Column(String)
...     geom = Column(Geometry('POLYGON'))

这个 Lake 类建立有关要映射的表的详细信息,包括由 __tablename__ ,和三列 idnamegeom . 这个 id 列将是表的主键。这个 geom 列是 geoalchemy2.types.Geometry 其列 geometry_typePOLYGON .

在数据库中创建表

这个 Lake 类有一个对应的 Table 对象表示数据库表。这个 Table 对象是由SQLAlchemy自动创建的,它被 Lake.__table__ 属性:

>>> Lake.__table__
Table('lake', MetaData(bind=None), Column('id', Integer(), table=<lake>,
primary_key=True, nullable=False), Column('name', String(), table=<lake>),
Column('geom', Polygon(srid=4326), table=<lake>), schema=None)

创建 lake 数据库中的表:

>>> Lake.__table__.create(engine)

如果我们想放下我们要用的桌子:

>>> Lake.__table__.drop(engine)

创建映射类的实例

通过声明映射,我们可以创建 Lake 对象:

>>> lake = Lake(name='Majeur', geom='POLYGON((0 0,1 0,1 1,0 1,0 0))')
>>> lake.geom
'POLYGON((0 0,1 0,1 1,0 1,0 0))'
>>> str(lake.id)
'None'

一个作品被传给 Lake 其几何结构的构造函数。这幅画代表了我们湖的形状。因为我们还没有告诉SQLAlchemy坚持 lake 对象,其 idNone .

也支持EWKT(扩展WKT)格式。例如,如果几何列的空间参照系是 4326 字符串 SRID=4326;POLYGON((0 0,1 0,1,0 1,0 0)) 可用作几何表示。

创建会话

ORM通过 Session . 让我们创建一个 Session 班级:

>>> from sqlalchemy.orm import sessionmaker
>>> Session = sessionmaker(bind=engine)

这个定制的 Session 类将创建新的 Session 绑定到数据库的对象。然后,每当我们需要与数据库进行对话时,我们将 Session ::

>>> session = Session()

以上 Session 与我们的PostgreSQL有关 Engine ,但它尚未打开任何连接。

添加新对象

坚持我们的 Lake 对象,我们 add() 它给 Session ::

>>> session.add(lake)

在这一点上 lake 对象已添加到 Session ,但尚未向数据库发出SQL。对象位于 悬而未决的 国家。持久化对象a 脸红犯罪 必须执行操作(提交意味着刷新):

>>> session.commit()

我们现在可以查询数据库 Majeur ::

>>> our_lake = session.query(Lake).filter_by(name='Majeur').first()
>>> our_lake.name
u'Majeur'
>>> our_lake.geom
<WKBElement at 0x9af594c; '0103000000010000000500000000000000000000000000000000000000000000000000f03f0000000000000000000000000000f03f000000000000f03f0000000000000000000000000000f03f00000000000000000000000000000000'>
>>> our_lake.id
1

our_lake.geom 是一个 geoalchemy2.elements.WKBElement ,这是地球炼金术提供的一种类型。 geoalchemy2.elements.WKBElement 包装数据库返回的WKB值。

让我们添加更多的湖:

>>> session.add_all([
...     Lake(name='Garde', geom='POLYGON((1 0,3 0,3 2,1 2,1 0))'),
...     Lake(name='Orta', geom='POLYGON((3 0,6 0,6 3,3 3,3 0))')
... ])
>>> session.commit()

查询

A Query 对象是使用 query() 作用于 Session . 例如这里有一个 Query 装载 Lake 按名称排序的实例:

>>> query = session.query(Lake).order_by(Lake.name)

任何 Query 是可以接受的:

>>> for lake in query:
...     print lake.name
...
Garde
Majeur
Orta

另一种执行查询并获取 Lake 对象涉及调用 all()Query ::

>>> lakes = session.query(Lake).order_by(Lake.name).all()

SQLAlchemy ORM教程 Querying section 提供更多查询示例。

进行空间查询

在SQL SELECT查询中使用空间过滤器非常常见。这些查询是通过使用空间关系函数或运算符在 WHERE SQL查询的子句。

例如,要找到 Lake 包含要点的 POINT(4 1) ,我们可以用这个 Query ::

>>> from sqlalchemy import func
>>> query = session.query(Lake).filter(
...             func.ST_Contains(Lake.geom, 'POINT(4 1)'))
...
>>> for lake in query:
...     print lake.name
...
Orta

GeoAlchemy允许重写这个 Query 更简洁地说:

>>> query = session.query(Lake).filter(Lake.geom.ST_Contains('POINT(4 1)'))
>>> for lake in query:
...     print lake.name
...
Orta

这里 ST_Contains 函数应用于 Lake.geom 列属性。在这种情况下,column属性实际上作为函数的第一个参数传递给函数。

下面是另一个空间过滤查询,基于 ST_Intersects ::

>>> query = session.query(Lake).filter(
...             Lake.geom.ST_Intersects('LINESTRING(2 1,4 1)'))
...
>>> for lake in query:
...     print lake.name
...
Garde
Orta

我们还可以将关系函数应用于 geoalchemy2.elements.WKBElement . 例如::

>>> lake = session.query(Lake).filter_by(name='Garde').one()
>>> print session.scalar(lake.geom.ST_Intersects('LINESTRING(2 1,4 1)'))
True

session.scalar 允许执行子句并返回标量值(本例中为布尔值)。

地球炼金术的功能都是从 ST_ . 运算符也被称为函数,但函数名不包括 ST_ 前缀。以PostGIS为例 && 运算符,它允许测试几何体的边界框是否相交。地球炼金术提供 intersects 功能:

>>> query = session.query
>>> query = session.query(Lake).filter(
...             Lake.geom.intersects('LINESTRING(2 1,4 1)'))
...
>>> for lake in query:
...     print lake.name
...
Garde
Orta

在模型中设置空间关系

我们假设除了 lake 我们还有一张桌子, treasure ,其中包括宝藏位置。假设我们对发现藏在湖底的宝藏感兴趣。

这个 Treasure 类如下:

>>> class Treasure(Base):
...      __tablename__ = 'treasure'
...      id = Column(Integer, primary_key=True)
...      geom = Column(Geometry('POINT'))

我们现在可以添加 relationshipLake 自动加载每个湖泊中包含的宝藏的表:

>>> from sqlalchemy.orm import relationship, backref
>>> class Lake(Base):
...     __tablename__ = 'lake'
...     id = Column(Integer, primary_key=True)
...     name = Column(String)
...     geom = Column(Geometry('POLYGON'))
...     treasures = relationship(
...         'Treasure',
...         primaryjoin='func.ST_Contains(foreign(Lake.geom), Treasure.geom).as_comparison(1, 2)',
...         backref=backref('lake', uselist=False),
...         viewonly=True,
...         uselist=True,
...     )

注意使用 as_comparison 功能。它是使用SQL函数所必需的 (ST_Contains 这里)在 primaryjoin 条件。这只适用于SQLAlchemy 1.3,因为 as_comparison 此版本之前不存在函数。见 Custom operators based on SQL function 有关详细信息,请参阅SQLAlchemy文档的一节。

有关用于配置此 relationship

  • backref 用于提供要放置在另一方向处理此关系的类上的属性的名称,即 Treasure

  • viewonly=True 指定关系仅用于加载对象,而不用于持久性操作;

  • uselist=True 指示属性应作为列表加载,而不是作为标量加载。

另外,请注意 treasures 属性对 lake 对象(和 lake 属性对 treasure 对象)在首次访问属性时“延迟”加载。另一个加载策略可以在 relationship . 比如你会用 lazy='joined' 对于要在与父项相同的查询中“急切”加载的相关项,使用 JOINLEFT OUTER JOIN .

Relationships API 有关 relationship 函数,以及可用于配置它的所有参数。

使用其他空间功能

这里有一个 Query 计算我们湖泊的缓冲区面积:

>>> from sqlalchemy import func
>>> query = session.query(Lake.name,
...                       func.ST_Area(func.ST_Buffer(Lake.geom, 2)) \
...                           .label('bufferarea'))
>>> for row in query:
...     print '%s: %f' % (row.name, row.bufferarea)
...
Majeur: 21.485781
Garde: 32.485781
Orta: 45.485781

这个 Query 应用PostGIS ST_Buffer 函数的每一行的几何列 lake 桌子。返回值是一个行列表,其中每一行实际上是由两个值组成的元组:湖泊名称和湖泊缓冲区的面积。每个元组实际上是一个SQLAlchemy KeyedTuple 对象,它提供属性类型访问器。

再一次, Query 能写得更简洁些:

>>> query = session.query(Lake.name,
...                       Lake.geom.ST_Buffer(2).ST_Area().label('bufferarea'))
>>> for row in query:
...     print '%s: %f' % (row.name, row.bufferarea)
...
Majeur: 21.485781
Garde: 32.485781
Orta: 45.485781

显然,处理和测量功能也可以用于 WHERE 条款。例如::

>>> lake = session.query(Lake).filter(
...             Lake.geom.ST_Buffer(2).ST_Area() > 33).one()
...
>>> print lake.name
Orta

而且,与地球炼金术支持的任何其他功能一样,处理和测量功能可以应用于 geoalchemy2.elements.WKBElement . 例如::

>>> lake = session.query(Lake).filter_by(name='Majeur').one()
>>> bufferarea = session.scalar(lake.geom.ST_Buffer(2).ST_Area())
>>> print '%s: %f' % (lake.name, bufferarea)
Majeur: 21.485781
Majeur: 21.485781

使用栅格函数

一些功能(比如 ST_Transform()ST_Union()ST_SnapToGrid() ,…)可用于两个 geoalchemy2.types.Geometrygeoalchemy2.types.Raster 类型。在GeoAlchemy2中,这些函数只为 Geometry 因为它不能同时为多个类型定义。因此在 Raster 通过传递 type_=Raster 函数的参数:

>>> query = session.query(Lake.raster.ST_Transform(2154, type_=Raster))

进一步参考