跳转到主要内容

pygeofilter 是OGC过滤标准的纯Python解析器实现

项目描述

pygeofilter

pygeofilter 是OGC过滤标准的纯Python解析器实现

PyPI version Build Status Documentation Status

特性

安装

可以通过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部分可以被优化

  • 纯静态操作数的算术运算
  • 所有谓词(空间、时间、数组、likebetweenin),如果所有操作数都已静态化
  • 当传递特殊查找表且所有参数都是静态的时,函数可以被优化
  • 如果可以预测任一分支,则可以消除AndOr组合器

不能优化包含对字典中未传递的属性或函数引用的分支。

以下示例显示了如何将静态计算优化为静态值,从而替换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 (55.4 kB 查看哈希值)

上传时间

构建分发

pygeofilter-0.2.4-py2.py3-none-any.whl (81.5 kB 查看哈希值)

上传时间 Python 2 Python 3

支持者