RFC 41:支持OGR中的多个几何字段

总结

在OGR数据模型中为具有多个几何字段的要素添加读/写支持。

动机

OGR数据模型当前绑定到每个要素、要素定义和图层的单个几何体字段。但许多数据格式支持多个几何字段。OGC简单特性规范也不限于每层一个几何字段(例如 OGC 06-104r4 "OpenGIS® Implementation Standard for Geographic information - Simple feature access -Part 2: SQL option

有一些解决方法:使用GEOMETRYCOLLECTION类型的几何图形,或根据图层中的几何列(如PostGIS或SQLite驱动程序中当前所做的那样)播发尽可能多的图层。所有这些方法充其量都是受到限制的变通方法:

  • GEOMETRYCOLLECTION方法:无法知道每个子几何体的名称/语义。所有子几何图形必须用相同的SRS表示。无法保证GEOMETRYCOLLECTION始终具有相同数量的子几何图形或具有一致的几何图形类型。

  • 每几何列一层方法:仅适用于只读方案。无法在写方案中工作。

此RFC的目的是在OGR数据模型中适当考虑每个特征对多个几何字段的支持。

建议的解决方案

(注:还研究了替代解决方案。它们将在本RFC的下一节中解释。)

总而言之,几何体字段的处理方式与属性字段在OGRFeatureDefn和OGRFeature级别的处理方式类似,但它们将保持独立。属性字段和几何字段在要素定义中将有各自的单独索引。

这种选择主要是为了最大限度地提高向后兼容性,同时提供新的功能。

它包括创建一个ogrgeomfieldefn类,以及OGRFieldDefn、OGRFeatureDefn、OGRFeature和OGRLayer类中的更改。

ogrgeomfieldefn类

ogrgeomfieldefn是一个新类。它的结构直接来自OGRFieldDefn类。

class CPL_DLL OGRGeomFieldDefn
{
protected:
        char                *pszName;
        OGRwkbGeometryType   eGeomType; /* all values possible except wkbNone */
        OGRSpatialReference* poSRS;

        int                 bIgnore;

public:
                            OGRGeomFieldDefn(char *pszName,
                                             OGRwkbGeometryType eGeomType);
        virtual            ~OGRGeomFieldDefn();

        void                SetName( const char * );
        const char         *GetNameRef();

        OGRwkbGeometryType  GetType();
        void                SetType( OGRwkbGeometryType eTypeIn );

        virtual OGRSpatialReference* GetSpatialRef();
        void                 SetSpatialRef(OGRSpatialReference* poSRS);

        int                 IsIgnored();
        void                SetIgnored( int bIgnoreIn );
};

可以注意到,成员变量以前是在OGRLayer级别找到的。

SRS对象已计数为ref。引用计数在构造函数和SetSpatialRef()中增加,在析构函数中减少。

GetSpatialRef()是故意设置为虚拟的,因此可以实现延迟计算(在某些驱动程序实现中,获取SRS可能会有明显的开销,例如读取额外的文件或发出SQL请求)。

OGRFeatureDefn类

OGRFeatureDefn类将扩展如下:

class CPL_DLL OGRFeatureDefn
{
  protected:
        // Remove OGRwkbGeometryType eGeomType and bIgnoreGeometry and
        // add instead the following :

        int nGeomFieldCount;
        OGRGeomFieldDefn* papoGeomFieldDefn;
  public:
        virtual int         GetGeomFieldCount();
        virtual OGRGeomFieldDefn *GetGeomFieldDefn( int i );
        virtual int         GetGeomFieldIndex( const char * );

        virtual void        AddGeomFieldDefn( OGRGeomFieldDefn * );
        virtual OGRErr      DeleteGeomFieldDefn( int iGeomField );

        // Route OGRwkbGeometryType GetGeomType() and void SetGeomType()
        // on the first geometry field definition.

        // Same for IsGeometryIgnored() and SetGeometryIgnored()
}

在实例化时,OGRFeatureDefn将创建名为“”的默认几何体字段定义,并键入wkbUnknown。如果调用SetGeomType(),则将在papoGeomFieldDefn上路由 [0] . 如果只存在一个几何字段定义,SetGeomType(wkbNone)将删除它。

GetGeomType()将在papoGeomFieldDefn上路由 [0] 如果它存在的话。否则它将返回wkbNone。

强烈建议在规则字段名和几何字段名的组合集合中存在名称唯一性。否则将导致SQL查询中的未指定行为。代码将不会检查此建议(目前没有对常规字段进行检查)。

另一个更改是使OGRFeatureDefn的所有现有方法都是虚拟的(并将private visibility更改为protected),因此如果需要,可以对该类进行子类化。这将启用对象的延迟创建。理由:建立完整的特性定义可能会很昂贵。但应用程序可能希望列出数据源的所有层,并且只提供一些重要的信息,但建立起来成本较低。在过去,为了解决这个问题,引入了OGRLayer::GetName()和OGRLayer::GetGeomType()。

还要注意,目前还没有预见到reordgeomfielddefns()。如果需要,可以在后面的步骤中添加。当调用SetGeomType(wkbNone)时,DeleteGeomFieldDefn()主要是为了OGRFeatureDefn本身的好处。

OGRFeature类

OGRFeature类将扩展如下:

class CPL_DLL OGRFeature
{
  private:
        // Remove poGeometry field and add instead
        OGRGeometry** papoGeometries; /* size is given by poFDefn->GetGeomFieldCount() */

  public:

        int                 GetGeomFieldCount();
        OGRGeomFieldDefn   *GetGeomFieldDefnRef( int iField );
        int                 GetGeomFieldIndex( const char * pszName);

        OGRGeometry*        GetGeomFieldRef(int iField);
        OGRErr              SetGeomFieldDirectly( int iField, OGRGeometry * );
        OGRErr              SetGeomField( int iField, OGRGeometry * );

        // Route SetGeometryDirectly(), SetGeometry(), GetGeometryRef(),
        // StealGeometry() on the first geometry field in the array

        // Modify implementation of SetFrom() to replicate all geometries
}

注意:在RFC41之前,SetGeometry()或SetGeometryDirectly()可以处理其功能定义为getGeomeType()==wkbNone(不一致)的功能。因为papoGeometries数组的大小现在基于GetGeomFieldCount(),当GetGeomType()==wkbNone时,geometry字段计数为0。VRT和CSV驱动程序将被修复以一致地声明其几何类型。

OGRLayer类

对OGRLayer类的影响:

  • 空间过滤器:考虑的选项是一次只允许一个空间过滤器。

    • 同时应用于多个几何领域的空间滤波器的需求并不明显。

    • m_poFilterGeom protected成员在OGR代码库中使用了250多次,因此将其转换为数组将是一项乏味的任务。。。

    添加物:

protected:
    int m_iGeomFieldFilter // specify the index on which the spatial
                           // filter is active.

public:
    virtual void        SetSpatialFilter( int iGeomField, OGRGeometry * );
    virtual void        SetSpatialFilterRect( int iGeomField,
                                            double dfMinX, double dfMinY,
                                            double dfMaxX, double dfMaxY );
GetNextFeature() implementation must check the m_iGeomFieldFilter index
in order to select the appropriate geometry field.
  • GetGeomType():未更改。对于其他字段,使用GetLayerDefn()->GetGeomField(i)->GetType()

  • GetSpatialRef():当前默认实现返回空值。它将被更改为返回GetLayerDefn()->GetGeomField(0)->GetSpatialRef()(如果至少有一个几何字段)。鼓励新的驱动程序不再专门化GetSpatialRef(),而是适当地设置其第一个几何字段的SRS。对于其他字段,请使用GetLayerDefn()->GetGeomField(i)->GetSpatialRef()。

    注意:由于SRS以前没有存储在OGRFeatureDefn级别,所以所有现有的驱动程序如果没有更新,将使GetGeomField(0)->GetSpatialRef()返回NULL。test_ogrsf实用程序将对此进行检查并发出警告。将逐步更新现有驱动程序。同时,建议使用OGRLayer::GetSpatialRef()以可靠的方式获取第一个几何字段的SRS。

  • 添加:

virtual OGRErr GetExtent(int iGeomField, OGREnvelope *psExtent,
                         int bForce = TRUE);
Default implementation would call GetExtent() if iGeomField == 0
  • 添加:

virtual OGRErr CreateGeomField(OGRGeomFieldDefn *poField);
  • 暂时没有DeleteGeomField()、ReorderGeomFields()或AlterGeomFieldDefn()。如果需要,可以稍后添加。

  • GetGeometryColumn():未更改。路由到第一个几何体字段。对于其他字段,使用GetLayerDefn()->GetGeomField(i)->GetNameRef()

  • SetIgnoredFields():除了常规字段外,还迭代几何字段。特殊的“OGR_GEOMETRY”值仅适用于第一个几何字段。

  • 交集()、并集()等。。。:不变。以后的改进可以使用papszOptions参数来指定一个替代的几何字段

  • TestCapability():添加OLCCreateGeomField功能以通知是否实现了CreateGeomField()。

OGRDataSource类

对OGRDataSource类的影响:

  • CreateLayer():签名将保持不变。如果需要多个几何体字段,则必须使用OGRLayer::CreateGeomField()。如果必须指定第一个几何字段的名称,则对于支持ODsCCreateGeomFieldAfterCreateLayer的数据源,使用代码应使用eGType=wkbNone调用CreateLayer(),然后使用OGRLayer::CreateGeomField()添加所有几何字段。

  • CopyLayer():适用于复制所有几何体字段(如果目标层支持)

  • ExecuteSQL():接受空间筛选器。对于一般的OGR SQL实现,这个过滤器是一个工具。它也可以应用于返回的layer对象。因此,实际上不需要在ExecuteSQL()API级别添加指定几何体字段的方法。

  • TestCapability():添加一个ODsCCreateGeomFieldAfterCreateLayer功能,以通知CreateGeomField()是否在创建层之后实现,以及CreateLayer()是否可以使用eGType=wkbNone安全调用。

探索替代解决方案

(如果您完全相信上述建议的方法,可以跳过本段:—))

另一种可能的解决方案是使用与几何体相关的信息扩展现有的OGRFieldDefn对象。这将涉及在OGRFieldType枚举中添加oftgometry值,并添加OGRwkbGeometryType eGeomType和OGRSpatialReference * poSRS成员到OGRFieldDefn。在OGRFeature类级别,OGRField联合可以通过ogrgometry扩展 * 字段。类似地,在OGRLayer级别,CreateField()可以用于创建新的几何体字段。

这种方法的主要缺点是向后兼容,这似乎是最自然的方法。这将影响OGR自身代码或外部代码中检索字段且不需要几何图形的所有位置。例如,在如下代码中(在大多数驱动程序的CreateFeature()中非常常见,或者在使用GetNextFeature()返回的功能的用户代码中非常常见):

switch( poFieldDefn->GetType() )
{
        case OFTInteger: something1(poField->GetFieldAsInteger()); break;
        case OFTReal: something2(poField->GetFieldAsDouble()): break;
        default: something3(poField->GetFieldAsString()); break;
}

对于遗留代码,这将导致将几何体作为常规字段处理。我们可以想象GetFieldAsString()将几何体转换为WKT,但这确实是需要的。基本上,属性和几何字段的处理在大多数用例中是不同的。

(另一方面,如果我们引入64位整数作为OGR类型(这是一个正在等待实现的RFC…),那么上面的代码仍然会产生一个有意义的结果。64位整数的字符串重租并不像默认行为那么糟糕。)

GetFieldCount()也会考虑几何字段,但在大多数情况下,需要减去它们。

避免上述兼容性问题的一种可能方法是在OGRFeatureDefn和OGRFeature级别拥有两组API。当前的,将忽略几何字段,以及一个“扩展”的,将其考虑在内。例如,OGRFeatureDefn::GetFieldCountEx()、OGRFeatureDefn::GetFieldIndexEx()、OGRFeatureDefn::GetFieldDefnEx()、OGRFeature::GetFieldEx()、OGRFeature::SetFieldAsXXXEx()将同时考虑属性和几何字段。这种方法的恼人之处在于OGRFeature中的20个方法GetField()和SetFieldXXX()重复。

计算机辅助编程接口

以下函数将添加到C API中:

/* OGRGeomFieldDefnH */

typedef struct OGRGeomFieldDefnHS *OGRGeomFieldDefnH;

OGRGeomFieldDefnH    CPL_DLL OGR_GFld_Create( const char *, OGRwkbGeometryType ) CPL_WARN_UNUSED_RESULT;
void                 CPL_DLL OGR_GFld_Destroy( OGRGeomFieldDefnH );

void                 CPL_DLL OGR_GFld_SetName( OGRGeomFieldDefnH, const char * );
const char           CPL_DLL *OGR_GFld_GetNameRef( OGRGeomFieldDefnH );

OGRwkbGeometryType   CPL_DLL OGR_GFld_GetType( OGRGeomFieldDefnH );
void                 CPL_DLL OGR_GFld_SetType( OGRGeomFieldDefnH, OGRwkbGeometryType );

OGRSpatialReferenceH CPL_DLL OGR_GFld_GetSpatialRef( OGRGeomFieldDefnH );
void                 CPL_DLL OGR_GFld_SetSpatialRef( OGRGeomFieldDefnH,
                                                     OGRSpatialReferenceH hSRS );

int                  CPL_DLL OGR_GFld_IsIgnored( OGRGeomFieldDefnH hDefn );
void                 CPL_DLL OGR_GFld_SetIgnored( OGRGeomFieldDefnH hDefn, int );

/* OGRFeatureDefnH */

int               CPL_DLL OGR_FD_GetGeomFieldCount( OGRFeatureDefnH hFDefn );
OGRGeomFieldDefnH CPL_DLL OGR_FD_GetGeomFieldDefn( OGRFeatureDefnH hFDefn, int i );
int               CPL_DLL OGR_FD_GetGeomFieldIndex( OGRFeatureDefnH hFDefn, const char * );

void              CPL_DLL OGR_FD_AddGeomFieldDefn( OGRFeatureDefnH hFDefn, OGRGeomFieldDefnH );
OGRErr            CPL_DLL OGR_FD_DeleteGeomFieldDefn( OGRFeatureDefnH hFDefn, int iGeomField );

/* OGRFeatureH */

int               CPL_DLL OGR_F_GetGeomFieldCount( OGRFeatureH hFeat );
OGRGeomFieldDefnH CPL_DLL OGR_F_GetGeomFieldDefnRef( OGRFeatureH hFeat, int iField );
int               CPL_DLL OGR_F_GetGeomFieldIndex( OGRFeatureH hFeat, const char * pszName);

OGRGeometryH      CPL_DLL OGR_F_GetGeomFieldRef( OGRFeatureH hFeat, int iField );
OGRErr            CPL_DLL OGR_F_SetGeomFieldDirectly( OGRFeatureH hFeat, int iField, OGRGeometryH );
OGRErr            CPL_DLL OGR_F_SetGeomField( OGRFeatureH hFeat, int iField, OGRGeometryH );

/* OGRLayerH */

void     CPL_DLL OGR_L_SetSpatialFilterEx( OGRLayerH, int iGeomField, OGRGeometryH );
void     CPL_DLL OGR_L_SetSpatialFilterRectEx( OGRLayerH, int iGeomField,
                                               double dfMinX, double dfMinY,
                                               double dfMaxX, double dfMaxY );
OGRErr   CPL_DLL OGR_L_GetExtentEx( OGRLayerH, int iGeomField,
                                    OGREnvelope *psExtent, int bForce );
OGRErr   CPL_DLL OGR_L_CreateGeomField( OGRLayerH, OGRGeomFieldDefnH hFieldDefn );

OGR SQL引擎

当前,“选择fieldname1 [,…菲尔德纳曼] “FROM layername”返回指定的字段以及关联的几何图形。这种行为显然没有遵循空间RDBMS的行为,在空间RDBMS中必须显式指定geometry字段。

在向后兼容性和本RFC的新功能之间采用了以下折衷方案:

  • 如果在SELECT子句中没有显式指定几何体字段,并且只有一个几何体字段与图层关联,则隐式返回该字段

  • 否则,仅返回显式提到的几何体字段(如果使用“*”,则返回所有几何体字段)。

局限性

  • 与当前一样,不会从连接的图层获取几何图形。

  • UNION ALL将只处理默认几何体,与当前一样。(可以在以后的工作中扩展。)

  • 在第一个几何字段上操作OGR_GEOMETRY、OGR_GEOM_WKT和OGR_GEOM_AREA的特殊字段。扩展这种特殊语法似乎并不明智。一个更好的替代方案是OGR SQLite方言(支持Spatialite),一旦它被更新为支持多几何体(不在本RFC的范围内)

驱动程序

在此RFC上下文中更新的驱动程序

  • 邮政地理信息系统:

    • 已经存在特殊形式的支持。具有多个几何图形的表当前报告为称为“表u名称(几何图形u列u名称)”的层(与几何图形列一样多的层)。此行为将被更改,以便表作为OGR层只报告一次。

  • PGDump(PGDump):下载

    • 添加对多几何表格的写入支持。

  • 内存:

    • 更新为新功能的简单说明。

  • 中间带:

    • 更新以支持多个几何字段(以及与此RFC无关的其他更改)

其他候选驱动程序(本RFC最初未涵盖的升级)

  • GML驱动程序:目前,每个功能只报告一个几何体。手动编辑GDAL 1.11中的.gfs文件-->实现的post RFC,更改此选项的可能性

  • SQLite驱动程序:

    • 当前,与当前PostGIS驱动程序的行为相同。

    • 驱动程序和SQLite方言都可以更新以支持多几何层。-->在GDAL 2.0中实现后RFC

  • Google Fusion表驱动程序:目前,只使用了第一个找到的几何列。可以将“table_name(geometry_column_name)”指定为传递给GetLayerByName()的层名称。

  • VRT:需要找到支持多种几何图形的语法的一些想法。受影响的XML语法:。在OGRVRTLayer元素级别:GeometryType、layers、geomefield、SrcRegion、ExtentXMin/YMin/XMax/YMax。在OGRVRTWarpedLayer元素级别:添加新元素以选择几何体字段。在OGRVRTUnionLayer元素级别:GeometryType、layers、extendxmin/YMin/XMax/YMax-->在GDAL 1.11中实现后RFC

  • CSV:当前,从名为“WKT”的列中获取几何图形。扩展以支持多个几何列。不值得这么做。可以用扩展的VRT驱动程序完成。-->在GDAL 1.11中实现后RFC

  • WFS:目前,只支持单个几何图层。标准允许多个几何图形。首先需要GML驱动程序支持。

  • 其他基于RDBMS的驱动程序:MySQL?,空间小姐?甲骨文空间?

公用事业

ogrinfo

将更新ogrinfo以报告与多几何体支持相关的信息。在单一几何数据源的情况下,输出应保持w.r.t电流输出不变。

多几何数据源的预期输出:

$ ogrinfo PG:dbname=mydb
INFO: Open of `PG:dbname=mydb'
      using driver `PostgreSQL' successful.
1: test_multi_geom (Polygon, Point)
$ ogrinfo PG:dbname=mydb -al
INFO: Open of `PG:dbname=mydb'
      using driver `PostgreSQL' successful.

Layer name: test_multi_geom
Geometry (polygon_geometry): Polygon
Geometry (centroid_geometry): Point
Feature Count: 10
Extent (polygon_geometry): (400000,4500000) - (500000, 5000000)
Extent (centroid_geometry): (2,48) - (3,49)
Layer SRS WKT (polygon_geometry):
PROJCS["WGS 84 / UTM zone 31N",
    GEOGCS["WGS 84",
        DATUM["WGS_1984",
            SPHEROID["WGS 84",6378137,298.257223563,
                AUTHORITY["EPSG","7030"]],
            AUTHORITY["EPSG","6326"]],
        PRIMEM["Greenwich",0,
            AUTHORITY["EPSG","8901"]],
        UNIT["degree",0.0174532925199433,
            AUTHORITY["EPSG","9122"]],
        AUTHORITY["EPSG","4326"]],
    PROJECTION["Transverse_Mercator"],
    PARAMETER["latitude_of_origin",0],
    PARAMETER["central_meridian",3],
    PARAMETER["scale_factor",0.9996],
    PARAMETER["false_easting",500000],
    PARAMETER["false_northing",0],
    UNIT["metre",1,
        AUTHORITY["EPSG","9001"]],
    AXIS["Easting",EAST],
    AXIS["Northing",NORTH],
    AUTHORITY["EPSG","32631"]]
Layer SRS WKT (centroid_geometry):
GEOGCS["WGS 84",
    DATUM["WGS_1984",
        SPHEROID["WGS 84",6378137,298.257223563,
            AUTHORITY["EPSG","7030"]],
        AUTHORITY["EPSG","6326"]],
    PRIMEM["Greenwich",0,
        AUTHORITY["EPSG","8901"]],
    UNIT["degree",0.0174532925199433,
        AUTHORITY["EPSG","9122"]],
    AUTHORITY["EPSG","4326"]]
FID Column = ogc_fid
Geometry Column 1 = polygon_geometry
Geometry Column 2 = centroid_geometry
area: Real
OGRFeature(test_multi_geom):1
  area (Real) = 500
  polygon_geometry = POLYGON ((400000 4500000,400000 5000000,500000 5000000,500000 4500000,400000 4500000))
  centroid_geometry = POINT(2.5 48.5)

将添加“-geomfield”选项以指定-spat选项应用于哪个字段。

ogr2ogr

Enhancements :

  • 如果输出层(OLCCreateGeomField功能)支持,将多个几何层转换为多个几何层。如果不支持,则只转换第一个几何体。

  • “-选择”选项。如果只指定属性字段名,则将隐式选择所有输入几何图形(向后兼容行为)。如果指定了一个或多个几何体字段名称,则只会选择这些名称。

  • 添加“-geomfield”选项以指定-spat选项应用于哪个字段

  • 各种几何变换(重投影、剪裁等)将应用于所有几何字段。

test_ogrsf

将通过一些一致性检查进行增强:

  • OGRLayer::GetSpatialRef()==OGRFeatureDefn::GetGeomField(0)->GetSpatialRef()

  • OGRLayer::GetGeomType()==OGRFeatureDefn::GetGeomField(0)->GetGeomType()

  • OGRLayer::GetGeometryColumn()==OGRFeatureDefn::GetGeomField(0)->GetNameRef()

空间过滤测试将在所有几何字段上循环。

文档

除了功能级文档外,新功能还将记录在 矢量数据模型矢量API教程 文件。

Python和其他语言绑定

新的C API将映射到SWIG绑定。它将只使用Python绑定进行测试。不需要新的类型映射,因此这应该以简单的方式与其他语言一起工作。

兼容性

  • 更改只是对现有API的添加,并且应该保留现有行为,因此这将是向后兼容的。

  • C++ ABI变化

  • PostGIS驱动程序w.r.t GDAL 1.10中具有多个几何图形的表的行为更改。

实施

即使是Rouault也将实现GDAL 1.11版本的上述更改,但Interlis驱动程序的升级将由Pirmin Kalberer完成。

基金

这项工作由 Federal Office of Topography (swisstopo), COGIS

投票历史

+来自Evner,FrankW,HowardB,DanielM和TamasS