Python实现教程中的矢量驱动程序

3.1 新版功能.

介绍

自GDAL 3.1以来,Python中增加了编写只读向量驱动程序的功能。强烈建议阅读 矢量驱动程序实现教程 首先,这将给出向量驱动程序如何工作的一般原则。

此功能不需要使用GDAL/OGR SWIG Python绑定(但矢量Python驱动程序可以使用它们)

注意:根据项目策略,这被认为是一个“实验”特性,GDAL项目不接受将此类Python驱动程序包含在GDAL存储库中。针对包含在GDAL主机中的驱动程序应优先移植到C++。其理由是:

  • Python代码的正确性在运行时可以被检查,而C++从静态分析(编译时和其他检查程序)中受益。

  • Python代码是在Python全局解释器锁下执行的,这使得它们无法伸缩。

  • 并非所有GDAL版本都有Python可用。

Python解释器的链接机制

Python解释器的链接机制

驾驶员位置

驱动程序文件名必须以 gdal_ogr_ 并拥有 .py 分机。将按以下方向搜索它们:

  • GDAL_PYTHON_DRIVER_PATH 配置选项(可能有多条路径由 : 关于UNIX或 ; 在Windows上)

  • 如果未定义,则 GDAL_DRIVER_PATH 配置选项。

  • 如果未定义,则在本机插件所在的目录中(在Unix编译时硬编码)。

GDAL不会尝试管理driver.py脚本导入的Python依赖项。用户需要确保其当前的Python环境安装了所有必需的依赖项。

导入部分

驱动程序必须具有以下导入节才能加载基类。

from gdal_python_driver import BaseDriver, BaseDataset, BaseLayer

这个 gdal_python_driver 模块是由GDAL动态创建的,不在文件系统中。

元数据部分

在.py文件的前1000行中,必须定义许多必需的和可选的KEY=VALUE驱动程序指令。它们是由C++代码解析的,不使用Python解释器,因此遵守以下约束是至关重要的:

  • 每个声明必须在一行上,并以 # gdal: DRIVER_ (尖字符和gdal之间的空格字符,冒号字符和 DRIVER_)

  • 该值必须是string类型的文本值(除了可以接受整数数组的“gdal:DRIVER-SUPPORTED”API-VERSION),而不包含表达式、函数调用、转义序列等。

  • 字符串可以是单引号或双引号

必须声明以下指令:

  • # gdal: DRIVER_NAME =“some_name”:司机的简称

  • # gdal: DRIVER_SUPPORTED_API_VERSION = [1] :驱动程序支持的API版本。必须包含1,这是GDAL 3.1中当前唯一支持的版本

  • # gdal: DRIVER_DCAP_VECTOR =“YES”:声明一个向量驱动程序

  • # gdal: DRIVER_DMD_LONGNAME “驱动程序的详细描述”

附加指令:

  • # gdal: DRIVER_DMD_EXTENSIONS =“ext1 ext2”:司机识别的分机列表,不带点,用空格隔开

  • # gdal: DRIVER_DMD_HELPTOPIC “url到hep页面”

  • # gdal: DRIVER_DMD_OPENOPTIONLIST =xml值,其中xml值是OptionOptionList规范,如“<OpenOptionList><Option name=”OPT1“type=”boolean“description=”bla“default=”NO“/></OpenOptionList>”**

  • 以及在gdal.h中找到的所有其他元数据项 GDAL_DMD_ (RESP) GDAL_DCAP )通过创建以 # gdal: DRIVER_ 以及 GDAL_DMD_ (RESP) GDAL_DCAP )元数据项。例如 #define GDAL_DMD_CONNECTION_PREFIX "DMD_CONNECTION_PREFIX" 变成 # gdal: DRIVER_DMD_CONNECTION_PREFIX

例子:

# gdal: DRIVER_NAME = "DUMMY"
# gdal: DRIVER_SUPPORTED_API_VERSION = [1]
# gdal: DRIVER_DCAP_VECTOR = "YES"
# gdal: DRIVER_DMD_LONGNAME = "my super plugin"
# gdal: DRIVER_DMD_EXTENSIONS = "foo bar"
# gdal: DRIVER_DMD_HELPTOPIC = "http://example.com/my_help.html"

驾驶员等级

入口点.py脚本必须包含一个继承自 gdal_python_driver.BaseDriver .

该类必须定义以下方法:

identify(self, filename, first_bytes, open_flags, open_options={})
参数
  • filename (str) -- 文件名,或更一般的连接字符串。

  • first_bytes (binary) -- 文件的第一个字节(如果是文件)。至少1024个(如果文件至少有1024个字节),或者如果驱动程序探测序列中的本机驱动程序先前请求了更多。

  • open_flags (int) -- 打开旗子。暂时被忽视。

  • open_options (dict) -- 打开选项。

返回

如果驱动程序可以识别该文件,则为True;否则为False;如果无法从第一个字节得知该文件,则为-1。

open(self, filename, first_bytes, open_flags, open_options={})
参数
  • filename (str) -- 文件名,或更一般的连接字符串。

  • first_bytes (binary) -- 文件的第一个字节(如果是文件)。至少1024个(如果文件至少有1024个字节),或者如果驱动程序探测序列中的本机驱动程序先前请求了更多。

  • open_flags (int) -- 打开旗子。暂时被忽视。

  • open_options (dict) -- 打开选项。

返回

从gdal_python_driver.BaseDataset或None派生的对象

例子:

# Required: class deriving from BaseDriver
class Driver(BaseDriver):

    def identify(self, filename, first_bytes, open_flags, open_options={}):
        return filename == 'DUMMY:'

    # Required
    def open(self, filename, first_bytes, open_flags, open_options={}):
        if not self.identify(filename, first_bytes, open_flags):
            return None
        return Dataset(filename)

数据集类

成功时的Driver.open()方法应该从继承自 gdal_python_driver.BaseDataset .

此对象的作用是存储矢量层。有两个实现选项。如果层的数量很小或者它们的构造速度很快,那么 __init__ 方法可以定义 layers 属性,该属性是继承自 gdal_python_driver.BaseLayer .

例子:

class Dataset(BaseDataset):

    def __init__(self, filename):
        self.layers = [Layer(filename)]

否则,应定义以下两种方法:

layer_count(self)
返回

层数

layer(self, idx)
参数

idx (int) -- 要返回的层的索引。通常介于0和self.layer_count()-1之间,但调用代码可能传递任何值。如果索引无效,则不应返回任何索引。

返回

从gdal_python_driver.BaseLayer或None派生的对象。C++代码将负责缓存该对象,并且对于给定的IDX值,该方法只调用一次。

例子:

class Dataset(BaseDataset):

    def layer_count(self):
        return 1

    def layer(self, idx):
        return [Layer(self.filename)] if idx = 0 else None

元数据

数据集可以定义 metadata 字典,在 __init__ of key:字符串类型的值,用于默认元数据域。或者,可以实现以下方法。

metadata(self, domain)
参数

domain (str) -- 元数据域。默认字符串为空

返回

无,或键字典:字符串类型的值对;

其他方法

可以可选地实现以下方法:

close(self)

在C++对等体GDALADATASET对象的销毁时调用。例如,用于关闭数据库连接。

图层类

数据集对象将实例化继承自 gdal_python_driver.BaseLayer .

元数据和其他定义

以下属性是必需的,必须在 __init__ 时间:

name

层名称,字符串类型。如果没有设置,则 name 必须定义方法。

fields

字段定义的序列(可能为空)。每个字段都是具有以下属性的词典:

name

要求的

type

类型的整数值 ogr.OFT_ (来自SWIG Python绑定)或以下字符串值之一: StringIntegerInteger16Integer64BooleanRealFloatBinaryDateTimeDateTime

如果未设置该属性,则 fields 必须定义方法并返回这样的序列。

geometry_fields

几何字段定义的序列(可能为空)。每个字段都是具有以下属性的词典:

name

必修的。可能是空的

type

必修的。类型的整数值 ogr.wkb_ (来自SWIG Python绑定)或以下字符串值之一: UnknownPointLineStringPolygonMultiPointMultiLineStringMultiPolygonGeometryCollections 或由返回的所有其他值 OGRGeometryTypeToName()

srs

附加到geometry字段的SRS作为字符串,可以被 OGRSpatialReference::SetFromUserInput() ,例如PROJ字符串、WKT字符串或AUTHORITY:CODE。

如果未设置该属性,则 geometry_fields 必须定义方法并返回这样的序列。

以下属性是可选的:

fid_name

功能ID列名,字符串类型。可能是空字符串。如果没有设置,则 fid_name 可以定义方法。

metadata

键的字典:值字符串,对应于默认元数据域的元数据。或者,a metadata 可以定义接受域参数的方法。

iterator_honour_attribute_filter

如果特征迭代器考虑 attribute_filter 可以在层上设置的属性。

iterator_honour_spatial_filter

如果特征迭代器考虑 spatial_filter 可以在层上设置的属性。

feature_count_honour_attribute_filter

如果特征计数方法考虑到 attribute_filter 可以在层上设置的属性。

feature_count_honour_spatial_filter

如果特征计数方法考虑到 spatial_filter 可以在层上设置的属性。

特征迭代器

Layer类必须实现迭代器接口,因此通常使用 __iter__ 方法。

迭代器必须返回包含功能内容的字典。

返回的字典中允许的两个键是:

id

强烈推荐。值必须是int类型才能被GDAL识别为FID

type

必修的。值必须是字符串“OGRFeature”

fields

必修的。该值必须是键为字段名的词典,或为无

geometry_fields

必修的。该值必须是一个字典,其键是几何字段名(可能是未命名几何列的空字符串)或无。每个密钥的值必须是编码为WKT的几何体,或者不是。

style

可选。该值必须是符合 特征样式规范 .

过滤

默认情况下,由OGR API的用户所设置的任何属性或空间过滤器将由驱动程序的通用C++侧通过遍历该层的所有特征来评估。

如果 iterator_honour_attribute_filter (RESP) iterator_honour_spatial_filter )图层对象的属性设置为 True ,属性过滤器(分别为。必须使用特征迭代器方法。

属性过滤器设置在 attribute_filter 图层对象的属性。这是一根符合 OGR SQL . 当OGR API更改属性过滤器时 attribute_filter_changed 调用可选方法(请参阅下面关于可选方法的段落)。实现 attribute_filter_changed 通过调用调用,可以决定通过驱动程序的通用C++侧后退。 SetAttributeFilter 方法(请参阅下面的直通示例)

几何过滤器设置在 spatial_filter 图层对象的属性。它是一个字符串编码为ISO WKT。OGR API的用户有责任在层的CRS中表示它。当OGR API更改属性过滤器时 spatial_filter_changed 调用可选方法(请参阅下面关于可选方法的段落)。实现 spatial_filter_changed 通过调用调用,可以决定通过驱动程序的通用C++侧后退。 SetSpatialFilter 方法(请参阅下面的直通示例)

可选方法

可以可选地实现以下方法:

extent(self, force_computation)
返回

名单 [xmin、ymin、xmax、ymax] 与图层的空间范围有关。

feature_count(self, force_computation)
返回

图层的特征数。

如果self.feature_count_honnor_attribute_filter或self.feature_count_honnor_space_filter设置为True,则必须使用此方法来执行属性筛选器和/或空间筛选器。

feature_by_id(self, fid)
参数

fid (int) -- 功能ID

返回

具有以下格式之一的要素对象 __next__ 方法,如果没有对象与fid匹配,则为无

attribute_filter_changed(self)

This method is called whenever self.attribute_filter has been changed. It is the opportunity for the driver to potentially change the value of self.iterator_honour_attribute_filter or feature_count_honour_attribute_filter attributes.

spatial_filter_changed(self)

This method is called whenever self.spatial_filter has been changed (its value is a geometry encoded in WKT) It is the opportunity for the driver to potentially change the value of self.iterator_honour_spatial_filter or feature_count_honour_spatial_filter attributes.

test_capability(self, cap)
参数

string (cap) -- 潜在值为BaseLayer.FastGetExtent、BaseLayer.FastSpatialFilter、BaseLayer.FastFeatureCount、BaseLayer.RandomRead、BaseLayer.StringsOutF8或其他受支持的字符串 OGRLayer::TestCapability()

返回

如果支持该功能,则为True;否则为False。

完整例子

下面的示例是一个passthrough驱动程序,它将调用转发到SWIG Python GDAL API。它没有实际用途,只是为了展示API最可能的用法。实际的驱动程序将只使用演示的API的一部分。例如,PASSUBTIN驱动程序通过调用驱动程序C++部分实现完全属性和空间过滤器。这个 iterator_honour_attribute_filteriterator_honour_spatial_filter 属性,以及 attribute_filter_changedspatial_filter_changed 方法实现,可以省略相同的结果。

驱动程序识别的连接字符串是“PA”SSHTROUGH:非python驱动程序支持的连接字符串". 请注意,由驱动程序名称作为前缀绝对不是一个要求,而是特定于此特定驱动程序的东西,这有点人为(如果没有前缀,连接字符串将直接指向本机驱动程序)。中提到的CityJSON驱动程序 Other examples 段落不需要它。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This code is in the public domain, so as to serve as a template for
# real-world plugins.
# or, at the choice of the licensee,
# Copyright 2019 Even Rouault
# SPDX-License-Identifier: MIT

# gdal: DRIVER_NAME = "PASSTHROUGH"
# API version(s) supported. Must include 1 currently
# gdal: DRIVER_SUPPORTED_API_VERSION = [1]
# gdal: DRIVER_DCAP_VECTOR = "YES"
# gdal: DRIVER_DMD_LONGNAME = "Passthrough driver"
# gdal: DRIVER_DMD_CONNECTION_PREFIX = "PASSTHROUGH:"

from osgeo import gdal, ogr

from gdal_python_driver import BaseDriver, BaseDataset, BaseLayer

class Layer(BaseLayer):

    def __init__(self, gdal_layer):
        self.gdal_layer = gdal_layer
        self.name = gdal_layer.GetName()
        self.fid_name = gdal_layer.GetFIDColumn()
        self.metadata = gdal_layer.GetMetadata_Dict()
        self.iterator_honour_attribute_filter = True
        self.iterator_honour_spatial_filter = True
        self.feature_count_honour_attribute_filter = True
        self.feature_count_honour_spatial_filter = True

    def fields(self):
        res = []
        layer_defn = self.gdal_layer.GetLayerDefn()
        for i in range(layer_defn.GetFieldCount()):
            ogr_field_def = layer_defn.GetFieldDefn(i)
            field_def = {"name": ogr_field_def.GetName(),
                         "type": ogr_field_def.GetType()}
            res.append(field_def)
        return res

    def geometry_fields(self):
        res = []
        layer_defn = self.gdal_layer.GetLayerDefn()
        for i in range(layer_defn.GetGeomFieldCount()):
            ogr_field_def = layer_defn.GetGeomFieldDefn(i)
            field_def = {"name": ogr_field_def.GetName(),
                         "type": ogr_field_def.GetType()}
            srs = ogr_field_def.GetSpatialRef()
            if srs:
                field_def["srs"] = srs.ExportToWkt()
            res.append(field_def)
        return res

    def test_capability(self, cap):
        if cap in (BaseLayer.FastGetExtent, BaseLayer.StringsAsUTF8,
                BaseLayer.RandomRead, BaseLayer.FastFeatureCount):
            return self.gdal_layer.TestCapability(cap)
        return False

    def extent(self, force_computation):
        # Impedance mismatch between SWIG GetExtent() and the Python
        # driver API
        minx, maxx, miny, maxy = self.gdal_layer.GetExtent(force_computation)
        return [minx, miny, maxx, maxy]

    def feature_count(self, force_computation):
        # Dummy implementation: we call back the generic C++ implementation
        return self.gdal_layer.GetFeatureCount(True)

    def attribute_filter_changed(self):
        # Dummy implementation: we call back the generic C++ implementation
        if self.attribute_filter:
            self.gdal_layer.SetAttributeFilter(str(self.attribute_filter))
        else:
            self.gdal_layer.SetAttributeFilter(None)

    def spatial_filter_changed(self):
        # Dummy implementation: we call back the generic C++ implementation
        # the 'inf' test is just for a test_ogrsf oddity
        if self.spatial_filter and 'inf' not in self.spatial_filter:
            self.gdal_layer.SetSpatialFilter(
                ogr.CreateGeometryFromWkt(self.spatial_filter))
        else:
            self.gdal_layer.SetSpatialFilter(None)

    def _translate_feature(self, ogr_f):
        fields = {}
        layer_defn = ogr_f.GetDefnRef()
        for i in range(ogr_f.GetFieldCount()):
            if ogr_f.IsFieldSet(i):
                fields[layer_defn.GetFieldDefn(i).GetName()] = ogr_f.GetField(i)
        geom_fields = {}
        for i in range(ogr_f.GetGeomFieldCount()):
            g = ogr_f.GetGeomFieldRef(i)
            if g:
                geom_fields[layer_defn.GetGeomFieldDefn(
                    i).GetName()] = g.ExportToIsoWkt()
        return {'id': ogr_f.GetFID(),
                'type': 'OGRFeature',
                'style': ogr_f.GetStyleString(),
                'fields': fields,
                'geometry_fields': geom_fields}

    def __iter__(self):
        for f in self.gdal_layer:
            yield self._translate_feature(f)

    def feature_by_id(self, fid):
        ogr_f = self.gdal_layer.GetFeature(fid)
        if not ogr_f:
            return None
        return self._translate_feature(ogr_f)

class Dataset(BaseDataset):

    def __init__(self, gdal_ds):
        self.gdal_ds = gdal_ds
        self.layers = [Layer(gdal_ds.GetLayer(idx))
                    for idx in range(gdal_ds.GetLayerCount())]
        self.metadata = gdal_ds.GetMetadata_Dict()

    def close(self):
        del self.gdal_ds
        self.gdal_ds = None


class Driver(BaseDriver):

    def _identify(self, filename):
        prefix = 'PASSTHROUGH:'
        if not filename.startswith(prefix):
            return None
        return gdal.OpenEx(filename[len(prefix):], gdal.OF_VECTOR)

    def identify(self, filename, first_bytes, open_flags, open_options={}):
        return self._identify(filename) is not None

    def open(self, filename, first_bytes, open_flags, open_options={}):
        gdal_ds = self._identify(filename)
        if not gdal_ds:
            return None
        return Dataset(gdal_ds)

其他示例

Other examples, including a CityJSON driver, may be found at https://github.com/OSGeo/gdal/tree/master/examples/pydrivers