Python实现教程中的矢量驱动程序
3.1 新版功能.
介绍
自GDAL 3.1以来,Python中增加了编写只读向量驱动程序的功能。强烈建议阅读 矢量驱动程序实现教程 首先,这将给出向量驱动程序如何工作的一般原则。
此功能不需要使用GDAL/OGR SWIG Python绑定(但矢量Python驱动程序可以使用它们)
注意:根据项目策略,这被认为是一个“实验”特性,GDAL项目不接受将此类Python驱动程序包含在GDAL存储库中。针对包含在GDAL主机中的驱动程序应优先移植到C++。其理由是:
Python代码的正确性在运行时可以被检查,而C++从静态分析(编译时和其他检查程序)中受益。
Python代码是在Python全局解释器锁下执行的,这使得它们无法伸缩。
并非所有GDAL版本都有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绑定)或以下字符串值之一:
String
,Integer
,Integer16
,Integer64
,Boolean
,Real
,Float
,Binary
,Date
,Time
,DateTime
如果未设置该属性,则
fields
必须定义方法并返回这样的序列。
- geometry_fields
几何字段定义的序列(可能为空)。每个字段都是具有以下属性的词典:
- name
必修的。可能是空的
- type
必修的。类型的整数值 ogr.wkb_ (来自SWIG Python绑定)或以下字符串值之一:
Unknown
,Point
,LineString
,Polygon
,MultiPoint
,MultiLineString
,MultiPolygon
,GeometryCollections
或由返回的所有其他值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_filter
和 iterator_honour_spatial_filter
属性,以及 attribute_filter_changed
和 spatial_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