지리 네트워크 모델 API 예제

이 문서에서는 GNM C++ 클래스를 이용해서 네트워크를 작업하는 방법을 설명하려 합니다. GNM 클래스의 목적 및 구조를 이해하기 위해 지리 네트워크 데이터 모델 을 먼저 읽어볼 것을 권장합니다.

네트워크 관리하기

첫 예시에서는 공간 데이터 집합(autotest\gnm\data GDAL 소스 트리에 있는 수관(pipe) 및 수원(well) 2개의 shapefile)을 기반으로 작은 수로망(water network)을 생성할 것입니다. 공통 네트워크 포맷 – GNMGdalNetwork 클래스 – 을 사용하면 이 네트워크를 위해 GDAL이 지원하는 벡터 포맷 가운데 하나를, 여기에서는 ESRI Shapefile을 선택할 수 있습니다. 수로망을 생성한 다음 네트워크 위상을 직접 편집하기 위해 위상(topology)을 작성하고 추가 데이터인 펌프(pump) 레이어를 추가할 것입니다.

가장 먼저 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에서는 직접 또는 자동 두 가지 방법으로 네트워크 위상을 작성할 수 있습니다. 대부분의 경우 자동 작성이 더 편리하지만, 조금씩 편집하기에는 직접 작성이 유용합니다. 자동 작성에는 파라미터가 몇 개 필요합니다 – 위상 작업에 어떤 “클래스 레이어”를 사용할지(이 예제의 경우 선택한 두 레이어를 사용합니다), 스냅 작업 허용 오차, 직접 비용(direct cost) 및 역 비용(inverse cost), 방향을 지정해야만 합니다. 이 예제의 경우 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 failed\n");
}
else
{
    printf("Topology has been built successfully\n");
}

이제 위상 및 공간 데이터를 가진 네트워크가 준비되었습니다. 이 네트워크를 서로 다른 (분석, 다른 포맷으로 변환 등등) 목적으로 사용할 수 있습니다. 그러나 그 전에 네트워크의 일부 데이터를 수정해야 하는 경우가 있습니다. 예를 들면 추가적인 피처를 추가하고 작성된 위상에 추가해야 (위상을 수정해야) 할 수도 있습니다. 다음은 네트워크에 새 “클래스 레이어”를 생성하고 이 레이어에 피처 하나를 추가하는 예시입니다.

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 );

새 레이어를 성공적으로 생성했다면 네트워크에 새 피처가 등록되었을 것이고, 이를 다른 피처들과 연결시킬 수 있습니다. 두 가지 방법으로 피처들을 서로 연결시킬 수 있습니다. 첫 번째 방법은 연결에서 경계가 될 실제 피처가 필요합니다. 두 번째 방법은 이런 피처가 필요없고, GNMGenericNetwork::ConnectFeatures() 메소드에 이 연결을 위한 특수 시스템 경계를 생성해서 그래프에 자동으로 추가하라는 의미를 가진 -1을 전송하는 것입니다. 앞의 예시에서 포인트 피처 1개만 추가했고 경계가 될 라인은 추가하지 않았기 때문에, “가상” 연결을 사용할 것입니다. 포인트 피처의 GFID(Global Feature ID)를 소스로, 기존 피처 가운데 하나의 GFID를 대상으로, 그리고 -1을 연결자(connector)로 전송합니다. (직접 및 역) 비용과 경계의 방향도 직접 설정해서 그래프에 그 값들을 작성하게 만들 것이라는 사실을 기억하십시오. 자동 연결을 사용하는 경우 (이때도 내부적으로 ConnectFeatures() 를 사용합니다) 앞에서 설정했던 규칙에 따라 이런 값들을 자동으로 설정합니다.

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

모두 끝났다면 네트워크를 정확하게 종료해서 할당된 리소스들을 해제합니다.

GDALClose(poDS);

이 코드들을 모두 합치면 다음과 같이 보일 것입니다:

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

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");


    GDALDriver *poDriver = GetGDALDriverManager()->GetDriverByName("GNMFile");
    GNMGenericNetwork* poDS = (GNMGenericNetwork*) poDriver->Create( "..\\network_data", 0, 0, 0, GDT_Unknown,
                                                    papszDSCO );
    CSLDestroy(papszDSCO);
    if (poDS == NULL)
    {
        printf("Failed to create network\n");
        exit(1);
    }

    GDALDataset *poSrcDS = (GDALDataset*) GDALOpenEx("..\\in_data",GDAL_OF_VECTOR | GDAL_OF_READONLY, NULL, NULL, NULL );
    if(poSrcDS == NULL)
    {
        printf("Can not open source dataset at\n");
        exit(1);
    }

    OGRLayer *poSrcLayer1 = poSrcDS->GetLayerByName("pipes");
    OGRLayer *poSrcLayer2 = poSrcDS->GetLayerByName("wells");
    if (poSrcLayer1 == NULL || poSrcLayer2 == NULL)
    {
        printf("Can not process layers of source dataset\n");
        exit(1);
    }

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

    GDALClose(poSrcDS);

    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 failed\n");
        exit(1);
    }
    else
    {
        printf("Topology has been built successfully\n");
    }

    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 );

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

    GDALClose(poDS);
}

네트워크 분석하기

두 번째 예시에서는 첫 번째 예시에서 작성했던 네트워크를 분석할 것입니다. 피처 차단(feature blocking)을 수행하는 데이크스트라(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 at\n");
        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 dataset\n");
    exit(1);
}

마지막으로 데이크스트라 최단 경로 메소드를 사용해서 계산합니다. 차단된 피처를 피해 이 경로를 찾고 내부 메모리 OGRLayer 에 저장합니다. 이렇게 내부 메모리에 저장된 경로를 실제 데이터셋으로 복사합니다. 이제 GIS가 최단 경로를 가시화할 수 있습니다.

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

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

이 코드들을 모두 합치면 다음과 같이 보일 것입니다:

#include "gnm.h"
#include "gnmstdanalysis.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 at\n");
        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 dataset\n");
        exit(1);
    }

    poNet->ChangeBlockState(36, true);

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

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