使用GeoAlchemy和Spatialite支持你数据库中的空间数据
在构建某些类型的应用程序时,重要的是要知道你将处理什么样的数据,这样你就可以为持有和处理它们做充分的准备。
其中一个例子就是当你需要处理 "空间数据"(位置数据--以经度和纬度为单位)时。此外,你可能希望能够根据这些信息进行查询。
例如,你可以设想在数据库中查询 "离给定位置(经度、纬度)最近的10个实体"。
在这篇文章中,我们将使用SQLite及其空间扩展,为一个简单的flask服务器创建一个支持空间数据读写操作的数据库。
我们还将看到如何从我们的服务器上的数据库读写空间数据。
构建一个简单的网络服务器
我们将从为一个服务打车平台创建一个简单的后台服务器开始,在这个平台上,用户可以与他们附近的 "服务提供商 "联系,以利用收费的服务。
对于这种平台,我们必须处理空间信息,如 "客户的位置"、"服务提供者的位置",而且我们可能还想对数据库进行查询,以便找到离某个位置最近的10个服务提供者。
本节展示了如何为后端服务器进行设置。
先决条件
- 已安装Python3
- 基本的Flask知识
我们将首先在根目录下创建一个名为server 的文件夹。我们将添加一个名为core 的新文件夹,以及另外三个名为wsgi.py 、models.py 和config.py 的文件。
在core 文件夹内,添加两个新的文件,名为。__init__.py, 和views.py 。
一旦你完成了,你的server 文件夹应该有一个像下面这样的结构。
server
| models.py
| wsgi.py
| config.py
|___core/
| __init__.py
| views.py
安装依赖项
从命令提示符/终端,导航到我们刚刚创建的server 文件夹。
现在,使用virtualenv ,为项目创建一个新的虚拟环境。
如果你没有安装virtualenv ,你可以通过pip 进行安装,方法如下。
pip install virtualenv
如果你已经安装了它,你可以跳过这一步,按如下步骤安装我们需要的库。
pip install flask flask-sqlalchemy flask-migrate geoalchemy2
创建和配置Flask服务器
在core/ 文件夹的__init__.py 文件中,我们将添加以下几行代码来创建一个应用程序工厂。
这是一个我们调用的函数,用于创建和配置flask应用程序。
这很好,因为它可以接受我们需要的参数,以便在我们调用该函数的任何时候创建一个应用程序。
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from config import config_
# initialize extensions
db = SQLAlchemy()
migrate = Migrate()
# app factory
def create_app(config_name):
# create app instance
app = Flask(__name__)
# configure app
app.config.from_object(config_[config_name])
# initialize extensions
db.init_app(app)
migrate.init_app(app, db)
return app
让我们在之前创建的config.py 文件中添加一些配置选项。
因此,导航到该文件并添加以下几行代码来添加配置选项。
import os
base_dir = os.path.abspath(os.getcwd())
class Config:
SQLALCHEMY_TRACK_MODIFICATIONS = False
SECRET_KEY = os.getenv('app-secret')
# other generic configuration
# ..options
class DevConfig(Config):
SQLALCHEMY_DATABASE_URI = f"sqlite:///{base_dir}/devdb.sqlite"
DEBUG = True
FLASK_ENV = 'development'
class ProdConfig(Config):
SQLALCHEMY_DATABASE_URI = os.getenv('DATABASE_URI')
FLASK_ENV = 'production'
config_ = {
'default': DevConfig,
'development': DevConfig,
'production': ProdConfig
}
最后一行中的config_ 变量持有一个字典,将每个配置选项映射到相应的环境类型。
这就是我们在__init__.py 文件中,在应用工厂中的做法,如下图所示。
# .. code omitted for brevity
from config import config_
# ..
# ...
app.config.from_object(config_[config_name])
运行Flask服务器
为了运行我们的flask应用,我们需要在wsgi.py 中添加一些最后的指令,这将有助于我们运行flask应用。
所以,进入该文件并添加以下几行代码。
from core import create_app
import os
# call create_app() to create and configure flask app
app = create_app('default' or os.getenv('FLASK_CONFIG'))
让我们也从终端将FLASK_ENV 环境变量设置为development ,这样我们就不必在每次做出改变时都重新启动我们的服务器。
FLASK_ENV=development
现在,让我们从命令提示符/终端使用以下命令运行我们的flask应用程序。
flask run
注意:这个命令之所以有效,是因为flask默认会寻找任何名为
wsgi.py或app.py的文件。如果你改变了命名规则,你必须将FLASK_APP环境变量设置为你所使用的文件名。
set FLASK_APP=myfilename.py
一旦你运行这个命令,你应该看到终端上的日志显示服务器正在运行,如下图所示。
添加对空间数据的支持
前提是
读者应该已经安装了以下库。
- [Spatialite]
- [Pandas]
我们要做的第一件事是为我们将要收集和存储的数据添加模型。这可以通过models.py 文件来完成。
打开该文件并添加以下几行代码。
from core import db
from datetime import datetime as d
# Base class
class Base:
id = db.Column(db.Integer, primary_key=True, nullable=False)
class User(Base, db.Model):
name = db.Column(db.String(50))
email = db.Column(db.String(100), unique=True)
signup_date = db.Column(db.DateTime, default=d.utcnow())
profile_photo = db.Column(db.String(200))
telephone = db.Column(db.String(20), unique=True)
address = db.Column(db.String(100))
lon = db.Column(db.Float)
lat = db.Column(db.Float)
is_handyman = db.Column(db.Boolean, default=False)
email_verified = db.Column(db.Boolean, default=False)
personal_id = db.Column(db.String(200))
# relationships
gigs_ = db.relationship('Gig', backref='owner')
class Gig(Base, db.Model):
title = db.Column(db.String(50))
description = db.Column(db.Text)
price = db.Column(db.Float)
glat = db.Column(db.Float)
glon = db.Column(db.Float)
owner_id = db.Column(db.Integer, db.ForeignKey('user.id'))
date_created = db.Column(db.DateTime(), default=d.utcnow())
我们的模型定义了User 对象和Gig 对象--用户可以是在平台上请求服务的普通客户,也可以是认定为勤杂工(或服务提供者)的用户。
在每一个模型中,我们都会收集lon 和lat 的值来定义他们的位置。
然而,这些值在几何上没有任何意义(它们只是浮点值),直到我们添加支持,使我们的数据库能够正确处理它们。
安装Spatialite
为了在处理空间数据时添加所需的支持,我们需要安装一个名为Spatialite 的扩展,该扩展为数据库添加支持。
要安装Spatialite ,下载Windows下的二进制文件。
这将把你带到一个像下面所示的页面。
一旦你进入下载页面,你将需要选择第一个选项来下载包含Spatialite二进制文件的压缩文件。
一旦下载完成,你可以使用任何文件提取工具如WinRAR来提取该文件夹。
解压后的文件夹应包含以下文件。
接下来,复制这个文件夹的路径,并从终端将其添加为环境变量,称为spat_path 。
set spat_path=/path/to/your/spatialite/folder
接下来,进入我们创建应用实例的wsgi.py 文件。
我们将添加一些配置选项,以便我们在创建应用程序时可以加载Spatialite 扩展。
更新wsgi.py 文件,使其包含这几行代码。
from sqlalchemy import event
from models import *
import os
import sqlite3
# spatialite path
spatialite_path = os.getenv('spat_path')
os.environ['PATH'] = spatialite_path + ';' + os.environ['PATH']
# create server instance from app factory
app = create_app(os.getenv("FLASK_CONFIG") or "default")
# add extension to sqlite3
with app.app_context():
@event.listens_for(db.engine, "connect")
def load_spatialite(dbapi_conn, connection_record):
dbapi_conn.enable_load_extension(True)
dbapi_conn.load_extension('mod_spatialite')
在这里,我们可以获取我们存储spatialite扩展位置的spat_path 变量,并将其追加到你电脑上的用户PATH变量。
接下来,我们调用flask应用上下文,允许我们从其原始范围之外访问正在运行的服务器的属性(通常这意味着运行依赖于服务器的运行实例的代码,而不使用原始flask上下文)。
在这个 "上下文 "下,一旦服务器开始运行并加载spatialite扩展,我们就监听connect 事件。
Spatialite是一个SQLite扩展,只对SQLite起作用。如果你使用的是不同的数据库,比如PostgreSQL,你也需要为它安装扩展(在这种情况下称为PostGIS)。然而,在其他数据库中,你可能也不需要以这种方式加载扩展。这只是为了演示如何在开发环境中完成这一工作。
然后我们需要安装一个新版本的sqlite3 ,因为预装在 Python 中的版本不包括RTree ,这是一个用于处理空间信息的数据结构。
到SQLite3的网站上下载最新的版本(可在Windows的预编译二进制文件下获得)。
下载后,解压内容,你应该找到一个sqlite3.dll 文件。
复制该文件并寻找预装在 Python 中的sqlite.dll ,它应该在一个叫做DLLs 的文件夹中。
这将是文件夹的路径。
C:\Users\Paulo\AppData\Local\Programs\Python\Python38\DLLs
然后,把旧的文件复制到一个安全的地方作为备份,并粘贴新下载的文件。
它可能会要求管理员权限,所以授予权限将文件复制到那里,然后我们就可以上路了。
更新模型和创建数据库
现在我们已经在数据库引擎中添加了对空间数据的支持,我们现在将在模型中添加一些特殊的字段,这些字段包含了可以被数据库处理的几何信息。
我们借助于早先安装的GeoAlchemy2 ,去models.py 文件,并更新它,使两个模型包含这些新的字段。
from geoalchemy2 import Geometry
class User(db.Model):
# .. code ommited for brevity
# ..
geometry = db.Column(Geometry(geometry_type='POINT', management = True, srid='4269'))
class Gig(db.Model):
# .. code ommited for brevity
# ..
gig_geometry = db.Column(Geometry(geometry_type='POINT', management = True, srid='4269'))
Geoalchemy2帮助我们定义这些 "几何 "字段,它们可以有WKT(Well Known Text)格式规定的不同geometry_type 。
这个应用的经度和纬度值将以点的形式出现,如下图所示。
X(lon, lat)
因此,我们指定该字段的geometry_type 属性为POINT ,这可能会根据使用情况而有所不同。
你可以随时查看geolalchemy2 参考文档,以了解可用的选项。
创建数据库
我们现在可以开始创建数据库了--我们将首先使用flask_migrate 在模型上运行迁移。
我们已经在前面的__init__.py 文件中为这个扩展添加了配置,所以我们可以初始化迁移,运行它们,并且用我们模型的当前版本升级我们的数据库。
所以,从终端运行下面的命令。
flask db init
这将为我们的数据库初始化迁移,并在我们的项目文件夹中为我们创建一个migrations/ 文件夹,该文件夹中包含了我们模型的元数据,子文件夹名为versions/ 。
我们可以在将这些文件应用到数据库之前对其进行编辑。
接下来,我们运行migrations来创建一个版本文件,供我们进行内省。
flask db migrate
一旦我们运行了 migrations,它就会代表我们创建一个新的版本文件,描述当前的模型,以及一旦我们运行升级命令就会应用到数据库中的变化。
打开项目目录下的migrations/versions/ ,并打开其中的第一个文件,它应该是下面这个样子的。
这个文件描述了flask_migrate从模型中看到的变化被应用到数据库中的情况。
这些变化是通过调用这个文件中的upgrade 函数来应用的,并通过调用downgrade 函数来恢复。我们想要的是应用这些变化,所以我们需要调用upgrade 。
然而,Alembic(也就是flask_migrate的运行环境)并没有在迁移脚本中为我们导入geoalchemy2字段所需的依赖。
我们需要在运行升级函数之前添加这个。否则,我们会得到一个错误,所以我们需要在使用这个脚本之前编辑它。
这就是flask_migrate的灵活之处,因为它允许我们在对数据库运行升级之前进行反省。
在这个版本文件中添加以下导入。
import geoalchemy2
根据Spatialite的文档,在数据库创建之后,我们还必须在任何其他事情之前运行InitSpatialMetaData() 函数。
如果你没有注意到,在我们运行migrate 命令的那一刻,数据库就被创建了。
现在,我们必须在upgrade 函数中加入对这个函数的调用,作为在其他事情之前运行的第一件事。
更新脚本,使upgrade 函数中的第一行内容如下。
def upgrade():-
conn = op.get_bind()
conn.execute(sa.text('SELECT InitSpatialMetaData();'))
# ..
保存这个文件,并从终端运行升级命令,如下所示。
flask db upgrade
这样就可以用我们的表和其他几个表来更新数据库,这些表是geoalchemy所需要的,以便数据库能够正确地管理我们的数据。
运行查询
让我们用一些用户信息来填充数据库。
我使用Epitools生成了一个经度和纬度值的随机列表,生成的值列表如下所示。
这个网站还允许你将其下载为Excel电子表格。
因此,前往该网站获取你的随机经度和纬度值列表。
我不得不对该文件进行一些编辑,删除一些不需要的列和字段,使其看起来像上图所示。这个文件被保存为CSV文件,而不是保存在我们项目目录的根层,以方便访问。
所以我打算用pandas来解析这个文件。
我们可以从一个新的文件中使用做这个。
db.session.commit()
现在,我们有了可以查询和处理的数据。
比方说,我们需要找到在位于POINT(145.67, -30.513) 的某个用户的10,000米半径范围内是否有任何杂工,我们可以从data.py ,对数据库进行空间查询,如下所示。
from sqlalchemy.sql import func
# ..
with app.app_context():
lng, lat = 148.523, -35.40
geo_wkb = func.ST_PointFromWKB(User.query.all()[0].geometry)
new_point = db.session.query(User).filter(
func.PtDistWithin(
User.geometry,
geo_wkb,
10000
)
).one()
print(new_point)
一般来说,你可能想把这些添加到你的API端点,或者把它们作为你的端点可以调用的函数,为你提供所需的信息。
总结
在本教程中,我们看到了如何设置和配置一个基本的Web应用程序,利用Spatialite和SQLite来执行对空间数据库的读写操作。
我们已经介绍了如何安装Spatiallite,还展示了如何更新你现有的SQLite实例以支持空间数据。
现在,你已经完全具备了使用Spatialite和geoalchemy2为你的项目设置开发环境的能力。