pygeofilter 是OGC过滤标准的纯Python解析器实现
项目描述
pygeofilter
pygeofilter 是OGC过滤标准的纯Python解析器实现
特性
- 解析几种过滤器编码标准
- 包含几个后端
- Django
- SQLAlchemy
- (Geo)Pandas
- 原生Python对象
安装
可以通过PIP安装此软件包
pip install pygeofilter
某些功能需要额外的依赖项。这目前仅影响后端。要安装这些功能,必须列出功能
# for the Django backend
pip install pygeofilter[backend-django]
# for the sqlalchemy backend
pip install pygeofilter[backend-sqlalchemy]
# for the native backend
pip install pygeofilter[backend-native]
使用方法
pygeofilter可以在多个级别上使用。它为各种过滤语言(如ECQL或CQL-JSON)提供解析器。每个解析器都位于其自己的子包中
>>> from pygeofilter.parsers.ecql import parse as parse_ecql
>>> filters = parse_ecql(filter_expression)
>>> from pygeofilter.parsers.cql_json import parse as parse_json
>>> filters = parse_json(filter_expression)
每个解析器都会创建一个对应过滤器表达式的抽象语法树(AST)表示,从而将所有可能的编程语言统一到单一共同基准。所有可能的节点都在pygeofilter.ast
模块中定义为类。
检查
检查生成的AST最简单的方法是使用get_repr
函数,该函数返回一个漂亮的字符串表示,表示已解析的内容
>>> filters = pygeofilter.parsers.ecql.parse('id = 10')
>>> print(pygeofilter.get_repr(ast))
ATTRIBUTE id = LITERAL 10.0
>>>
>>>
>>> filter_expr = '(number BETWEEN 5 AND 10 AND string NOT LIKE \'%B\') OR INTERSECTS(geometry, LINESTRING(0 0, 1 1))'
>>> print(pygeofilter.ast.get_repr(pygeofilter.parse(filter_expr)))
(
(
ATTRIBUTE number BETWEEN 5 AND 10
) AND (
ATTRIBUTE string NOT LIKE '%B'
)
) OR (
INTERSECTS(ATTRIBUTE geometry, Geometry(geometry={'type': 'LineString', 'coordinates': ((0.0, 0.0), (1.0, 1.0))}))
)
评估
解析后的AST可以进一步评估并转换为所需上下文中的过滤机制。通常这是SQL或与某种数据存储交互的对象关系映射器(ORM)等语言。
有几个预定义的后端可供使用,其中解析的表达式可以应用。目前包括
- Django
- sqlalchemy
- (Geo)Pandas
- 纯Python对象过滤
这些的使用方法请参考各自的文档。
pygeofilter提供了一些机制来帮助构建这样的评估器(包含的后端也使用它们)。Evaluator
类允许方便地以深度优先的方式遍历AST并构建针对特定API的过滤器。只有处理过的节点类会被评估,不支持的处理将会抛出异常。
考虑以下示例
from pygeofilter import ast
from pygeofilter.backends.evaluator import Evaluator, handle
from myapi import filters # <- this is where the filters are created.
# of course, this can also be done in the
# evaluator itself
# Evaluators must derive from the base class `Evaluator` to work
class MyAPIEvaluator(Evaluator):
# you can use any constructor as you need
def __init__(self, field_mapping=None, mapping_choices=None):
self.field_mapping = field_mapping
self.mapping_choices = mapping_choices
# specify the handled classes in the `handle` decorator to mark
# this function as the handler for that node class(es)
@handle(ast.Not)
def not_(self, node, sub):
return filters.negate(sub)
# multiple classes can be declared for the same handler function
@handle(ast.And, ast.Or)
def combination(self, node, lhs, rhs):
return filters.combine((lhs, rhs), node.op.value)
# handle all sub-classes, like ast.Equal, ast.NotEqual,
# ast.LessThan, ast.GreaterThan, ...
@handle(ast.Comparison, subclasses=True)
def comparison(self, node, lhs, rhs):
return filters.compare(
lhs,
rhs,
node.op.value,
self.mapping_choices
)
@handle(ast.Between)
def between(self, node, lhs, low, high):
return filters.between(
lhs,
low,
high,
node.not_
)
@handle(ast.Like)
def like(self, node, lhs):
return filters.like(
lhs,
node.pattern,
node.nocase,
node.not_,
self.mapping_choices
)
@handle(ast.In)
def in_(self, node, lhs, *options):
return filters.contains(
lhs,
options,
node.not_,
self.mapping_choices
)
def adopt(self, node, *sub_args):
# a "catch-all" function for node classes that are not
# handled elsewhere. Use with caution and raise exceptions
# yourself when a node class is not supported.
...
# ...further ast handlings removed for brevity
测试
为了进行测试,必须满足一些要求。这些可以通过pip进行安装
pip install -r requirements-dev.txt
pip install -r requirements-test.txt
可以使用pytest
来测试功能。
python -m pytest
Docker
在Docker中执行测试
docker build -t pygeofilter/test -f Dockerfile-3.9 .
docker run --rm pygeofilter/test
后端
pygeofilter
附带以下后端。其中一些需要额外的依赖项,有关详细信息,请参阅安装部分。
Django
Django有一个默认的后端实现,其中所有过滤器都转换为Django ORM。为了使用此集成,我们需要两个字典,一个将可用的字段映射到Django模型字段,另一个将使用choices
的字段映射。考虑以下示例模型
from django.contrib.gis.db import models
optional = dict(null=True, blank=True)
class Record(models.Model):
identifier = models.CharField(max_length=256, unique=True, null=False)
geometry = models.GeometryField()
float_attribute = models.FloatField(**optional)
int_attribute = models.IntegerField(**optional)
str_attribute = models.CharField(max_length=256, **optional)
datetime_attribute = models.DateTimeField(**optional)
choice_attribute = models.PositiveSmallIntegerField(choices=[
(1, 'ASCENDING'),
(2, 'DESCENDING'),],
**optional)
class RecordMeta(models.Model):
record = models.ForeignKey(Record, on_delete=models.CASCADE, related_name='record_metas')
float_meta_attribute = models.FloatField(**optional)
int_meta_attribute = models.IntegerField(**optional)
str_meta_attribute = models.CharField(max_length=256, **optional)
datetime_meta_attribute = models.DateTimeField(**optional)
choice_meta_attribute = models.PositiveSmallIntegerField(choices=[
(1, 'X'),
(2, 'Y'),
(3, 'Z')],
**optional)
现在我们可以指定要应用于过滤器时的字段映射和映射选择
FIELD_MAPPING = {
'identifier': 'identifier',
'geometry': 'geometry',
'floatAttribute': 'float_attribute',
'intAttribute': 'int_attribute',
'strAttribute': 'str_attribute',
'datetimeAttribute': 'datetime_attribute',
'choiceAttribute': 'choice_attribute',
# meta fields
'floatMetaAttribute': 'record_metas__float_meta_attribute',
'intMetaAttribute': 'record_metas__int_meta_attribute',
'strMetaAttribute': 'record_metas__str_meta_attribute',
'datetimeMetaAttribute': 'record_metas__datetime_meta_attribute',
'choiceMetaAttribute': 'record_metas__choice_meta_attribute',
}
MAPPING_CHOICES = {
'choiceAttribute': dict(Record._meta.get_field('choice_attribute').choices),
'choiceMetaAttribute': dict(RecordMeta._meta.get_field('choice_meta_attribute').choices),
}
最后,我们能够将CQL AST连接到Django数据库模型。我们还提供了解析时间戳、持续时间、几何形状和包围盒的工厂函数,以便它们可以与ORM层一起使用
from pygeofilter.backends.django import to_filter
from pygeofilter.parsers.ecql import parse
cql_expr = 'strMetaAttribute LIKE \'%parent%\' AND datetimeAttribute BEFORE 2000-01-01T00:00:01Z'
ast = parse(cql_expr)
filters = to_filter(ast, mapping, mapping_choices)
qs = Record.objects.filter(**filters)
SQL
pygeofilter
提供了一种从AST创建SQL WHERE
子句的基本方法。以下示例展示了与OGR ExecuteSQL
函数结合使用的情况
from osgeo import ogr
from pygeofilter.backends.sql import to_sql_where
from pygeofilter.parsers.ecql import parse
FIELD_MAPPING = {
'str_attr': 'str_attr',
'maybe_str_attr': 'maybe_str_attr',
'int_attr': 'int_attr',
'float_attr': 'float_attr',
'date_attr': 'date_attr',
'datetime_attr': 'datetime_attr',
'point_attr': 'GEOMETRY',
}
FUNCTION_MAP = {
'sin': 'sin'
}
# parse the expression
ast = parse('int_attr > 6')
# open an OGR DataSource
data = ogr.Open(...)
# create the WHERE clause, field and function mappings must be provided
where = to_sql_where(ast, FIELD_MAPPING, FUNCTION_MAP)
# filter the DataSource to get a result Layer
layer = data.ExecuteSQL(f"""
SELECT id, str_attr, maybe_str_attr, int_attr, float_attr, date_attr, datetime_attr, GEOMETRY
FROM layer
WHERE {where}
""", None, "SQLite")
请注意,必须指定SQLite
方言,因为这是内部使用的方言。
:warning: 输入值 没有 被净化/与生成的SQL文本分离。这是由于与OGR API的兼容性,不允许将SQL与参数分离。
优化
这是一个特殊类型的后端,因为AST评估的结果实际上是一个新的AST。此后端的目的在于消除AST中的静态分支,可能减少实际评估过滤值的成本。
哪些AST部分可以被优化
- 纯静态操作数的算术运算
- 所有谓词(空间、时间、数组、
like
、between
、in
),如果所有操作数都已静态化 - 当传递特殊查找表且所有参数都是静态的时,函数可以被优化
- 如果可以预测任一分支,则可以消除
And
和Or
组合器
不能优化包含对字典中未传递的属性或函数引用的分支。
以下示例显示了如何将静态计算优化为静态值,从而替换AST的整个分支
>>> import math
>>> from pygeofilter import ast
>>> from pygeofilter.parsers.ecql import parse
>>> from pygeofilter.backends.optimize import optimize
>>>
>>> root = parse('attr < sin(3.7) - 5')
>>> optimized_root = optimize(root, {'sin': math.sin})
>>> print(ast.get_repr(root))
ATTRIBUTE attr < (
(
sin (3.7)
) - 5
)
>>> print(ast.get_repr(optimized_root))
ATTRIBUTE attr < -5.529836140908493
项目详情
下载文件
下载适用于您平台文件的文件。如果您不确定选择哪个,请了解更多关于安装包的信息。
源分发
构建分发
pygeofilter-0.2.4.tar.gz 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 265a0a5d47e8421b7f07710d66422e94c4ee2590ce3fad6bd4b61a248e4e816b |
|
MD5 | a75676262ac2e65b685fa585dc2aabc9 |
|
BLAKE2b-256 | 00a97cf2627bff8da94c3bfb0a92705fe442716913fa3e8b939951d5f96c7eb0 |
pygeofilter-0.2.4-py2.py3-none-any.whl 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 3462e7d6d08e27da281b3de6f360bb6cf09b6dedafd2c630474d4900c1d89abf |
|
MD5 | 3fa63a81e15d999192abca4bc81da1da |
|
BLAKE2b-256 | bd7f3ae203c863da7c3cf1dbdd7891754df19f0ca917e5bff3490414574177bd |