GDAL GNM API 教程

这是一个关于使用GNM(Geographic Networks Model)C++类处理网络数据的技术教程。在此教程中,我们将创建一个小型水网络,并进行网络管理和分析。

GDAL GNM API 教程

本文旨在描述如何使用 GNM C++ 类来处理网络数据。建议在阅读本文之前先了解地理网络数据模型(Geographic Networks Data Model)以理解 GNM 类的目的和结构。

管理网络

在第一个示例中,我们将基于一组空间数据(两个 shapefile:pipes 和 wells,这些文件位于 GDAL 源树的 autotestgnmdata 目录中)创建一个小型水网络。使用通用网络格式 – GNMGdalNetwork 类 – 允许我们选择 GDAL 支持的矢量格式之一作为我们的网络存储格式 – ESRI Shapefile。创建网络后,我们将构建拓扑并添加一些额外的数据,例如泵浦层,以便手动编辑网络拓扑。

首先,我们要注册 GDAL 驱动程序并创建一些选项(字符串对),这些选项将在网络创建过程中作为参数传递。在这里,我们创建了一个网络的名称。

#include "gnm.h"
#include <vector>

int main ()
{
    GDALAllRegister();

    char **papszDSCO = NULL;
    papszDSCO = CSLAddNameValue(papszDSCO, GNM_MD_NAME, "my_pipes_network");
    papszDSCO = CSLAddNameValue(papszDSCO, GNM_MD_SRS, "EPSG:4326");
    papszDSCO = CSLAddNameValue(papszDSCO, GNM_MD_DESCR, "My pipes network");
    papszDSCO = CSLAddNameValue(papszDSCO, GNM_MD_FORMAT, "ESRI Shapefile");
    // 其他选项...
}

某些选项是强制性的。在网络创建过程中必须指定以下参数:路径/名称、网络存储格式、空间参考系统(EPSG、WKT 等)。将创建带有”网络部分”的相应数据集,并返回生成的网络。

GDALDriver *poDriver = GetGDALDriverManager()->GetDriverByName("GNMFile");
GNMGenericNetwork* poDS = (GNMGenericNetwork*) poDriver->Create( "..\network_data", 0, 0, 0, GDT_Unknown,
                                                   papszDSCO );
CSLDestroy(papszDSCO);

现在我们有一个仅由”系统层”组成的空网络。我们需要使用”类层”的要素来填充它,因此我们打开某个外部数据集,并将其中的图层复制到我们的网络中。请注意,我们使用 GDALDataset:: 方法来处理”类层”,因为 GNMNetwork 继承自 GDALDataset。

GDALDataset *poSrcDS = (GDALDataset*) GDALOpenEx("..\in_data",
                                GDAL_OF_VECTOR | GDAL_OF_READONLY, NULL, NULL, NULL );

OGRLayer *poSrcLayer1 = poSrcDS->GetLayerByName("pipes");
OGRLayer *poSrcLayer2 = poSrcDS->GetLayerByName("wells");

poDS->CopyLayer(poSrcLayer1, "pipes");
poDS->CopyLayer(poSrcLayer2, "wells");

GDALClose(poSrcDS);

成功复制后,我们有一个充满要素但没有拓扑的网络。这些要素已被添加到网络并注册,但它们仍然没有相互连接。现在是构建网络拓扑的时候。在 GNM 中有两种构建拓扑的方式:手动和自动。在大多数情况下,自动构建更方便,而手动构建适用于小的编辑。自动构建需要一些参数:我们必须指定哪些”类层”将参与拓扑构建(我们选择了两个图层)、捕捉容差、直接和反向成本以及方向,在我们的情况下方向等于 0.00005。如果构建成功,网络的图将填充与相应的连接。

printf("nBuilding network topology ...n");
char **papszLayers = NULL;
for(int i = 0; i < poDS->GetLayerCount(); ++i)
{
    OGRLayer* poLayer = poDS->GetLayer(i);
    papszLayers = CSLAddString(papszLayers, poLayer->GetName() );
}

if(poGenericNetwork->ConnectPointsByLines(papszLayers, dfTolerance,
                                    dfDirCost, dfInvCost, eDir) != CE_None )
{
    printf("Building topology failedn");
}
else
{
    printf("Topology has been built successfullyn");
}

此时,我们有一个准备好的网络,具有拓扑和空间数据,现在可以用于不同的目的(分析、转换成不同的格式等)。但有时需要修改网络数据。例如,我们需要添加额外的要素并将其附加到我们构建的拓扑上(修改拓扑)。我们在网络中创建一个新的”类层”,并向其添加一个要素。

OGRLayer *poNewLayer = poDS->CreateLayer("pumps", , NULL, wkbPoint, NULL );
if( poNewLayer == NULL )
{
    printf( "Layer creation failed.n" );
    exit( 1 );
}

OGRFieldDefn fieldDefn ("pressure",OFTReal);
if( poNewLayer->CreateField( &fieldDefn ) != OGRERR_NONE )
{
    printf( "Creating Name field failed.n" );
    exit( 1 );
}

OGRFeature *poFeature = OGRFeature::CreateFeature(poNewLayer->GetLayerDefn());
OGRPoint pt;
pt.setX(37.291466);
pt.setY(55.828351);
poFeature->SetGeometry(&pt);
if( poNewLayer->

CreateFeature( poFeature ) != OGRERR_NONE )
{
    printf( "Failed to create feature.n" );
    exit( 1 );
}

GNMGFID gfid = poFeature->GetFID();

OGRFeature::DestroyFeature( poFeature );

成功创建后,该要素将在网络中注册,我们可以将其连接到其他要素。有两种可能的方法来实现这一点。在第一种情况下,我们需要一个真实的要素作为连接的边,而在第二种情况下,我们不需要这样的要素,并且将 -1 传递给 GNMGenericNetwork::ConnectFeatures() 方法表示将为此连接创建一个特殊的系统边并将其自动添加到图中。在我们的情况下,我们只添加了一个点要素,没有将线要素作为边,因此我们将使用”虚拟”连接。我们将我们的点的 GFID 作为源,现有要素的 GFID 之一作为目标,并将 -1 作为连接器。请注意,我们还手动设置了边的成本(直接和反向)和方向,这些值将被写入图中。当我们使用自动连接(它也在内部使用 ConnectFeatures())时,这些值将根据我们之前设置的规则自动设置。

if (poDS->ConnectFeatures(gfid ,63, -1, 5.0, 5.0, GNMDirection_SrcToTgt) != GNMError_None)
{
    printf("Can not connect featuresn");
}

最后,我们正确关闭网络,释放分配的资源。

GDALClose(poDS);

网络分析

在第二个示例中,我们将分析在第一个示例中创建的网络。我们将使用 Dijkstra 算法计算两点之间的最短路径,执行要素阻塞并将结果路径保存到文件中。

首先,我们打开我们的网络,传递到其 Shapefile 数据集的路径。

#include "gnm.h"
#include "gnm_priv.h"

int main ()
{
    GDALAllRegister();

    GNMGenericNetwork *poNet = (GNMGenericNetwork*) GDALOpenEx("..\network_data",GDAL_OF_GNM | GDAL_OF_UPDATE, NULL, NULL, NULL );
    if(poSrcDS == NULL)
    {
        printf("Can not open source dataset atn");
        exit(1);
    }

在进行任何计算之前,我们打开将保存结果路径的数据集。

GDALDataset *poResDS;
poResDS = (GDALDataset*) GDALOpenEx("..\out_data",
                                    GDAL_OF_VECTOR | GDAL_OF_UPDATE,
                                    NULL, NULL, NULL);
if (poResDS == NULL)
{
    printf("Failed to open resulting datasetn");
    exit(1);
}

最后,我们使用 Dijkstra 最短路径方法进行计算。这条路径将经过阻塞要素并保存在内部内存 OGRLayer 中,然后我们将其复制到实际数据集中。现在可以在 GIS 中进行可视化。

    OGRLayer *poResLayer = poNet->GetPath(64, 41, GATDijkstraShortestPath, NULL);
    if (poResLayer == NULL)
    {
        printf("Failed to save or calculate pathn");
    }
    else if (poResDS->CopyLayer(poResLayer, "shp_tutorial.shp") == NULL)
    {
        printf("Failed to save path to the layern");
    }
    else
    {
        printf("Path saved successfullyn");
    }

    GDALClose(poResDS);
    poNet->ReleaseResultSet(poRout);
    GDALClose(poNet);
}