shapelib : 読み込み

ファイル情報の取得

まずは、Shapeファイルを開いて種別を取得する必要があります。
そこまでのコードは大体以下のようにします。

#include "shapefil.h"  // ←"shapefile.h"ではないので注意!

int main( int argc, char *argv[] )
{
	SHPHandle hSHP;
	int nShapeType, nEntities;
	double adfMinBound[4], adfMaxBound[4];
	
	hSHP = SHPOpen( argv[1], "r" );
	if ( hSHP == NULL )
	{
		exit( 1 );
	}

	SHPGetInfo( hSHP, &nEntities, &nShapeType, adfMinBound, adfMaxBound );

SHPGetInfo()関数でこのファイルの要素数と要素種別を取得します。
第2引数の要素種別は以下の値をとります。

  • SHPT_NULL
  • SHPT_POINT
  • SHPT_ARC
  • SHPT_POLYGON
  • SHPT_MULTIPOINT
  • SHPT_POINTZ
  • SHPT_ARCZ
  • SHPT_POLYGONZ
  • SHPT_MULTIPOINTZ
  • SHPT_POINTM
  • SHPT_ARCM
  • SHPT_POLYGONM
  • SHPT_MULTIPOINTM
  • SHPT_MULTIPATCH

例えば、ポイントShapeだけを処理対象にしたい場合は以下のようにします。

	if ( nShapeType != SHPT_POINT &&
		 nShapeType != SHPT_POINTZ &&
		 nShapeType != SHPT_POINTM )
	{
		SHPClose( hSHP );
		exit( 1 );
	}

図形データの読み込み

ファイルごとに要素種別は決まっていますが、
shapelibでは要素の情報を格納する構造体は種別にかかわらずSHPObject型です。
なので、以下のようにすれば統一的に読み込むことができます。

	SHPObject *psElem;
	double *padfX, *padfY, *padfZ;
	
	// ファイルからデータを読み込んで頂点配列に追加
	for ( i = 0; i < nEntities; i++ )
	{
		psElem = SHPReadObject( hSHP, i );

		padfX = new double[psElem->nVertices];
		padfY = new double[psElem->nVertices];
		padfZ = new double[psElem->nVertices];
		for ( j = 0; j < psElem->nVertices; j++ )
		{
			padfX[j] = psElem->padfX[j];
			padfY[j] = psElem->padfY[j];
			padfZ[j] = psElem->padfZ[j];
		}
		
		// 使い終わったら
		delete[] padfX;
		delete[] padfY;
		delete[] padfZ;
		SHPDestroyObject( psElem );
	}

属性の読み込み

属性データは.dbfに格納されています。
shapelibではDBFは別個に取り扱います。
なので、shpファイルと同時に開いておく必要があります。

以下のような感じでしょうか。

	char szDBF[_MAX_PATH+1];

	strcpy_s( szDBF, argv[1] );
	szDBF[strlen(szDBF)-3] = '';
	strcat_s( szDBF, "dbf" );
	hDBF = DBFOpen( szDBF, "rb" );
	if ( !hDBF )
	{
		SHPClose( hSHP );
		exit( 1 );
	}

DBFを開いたら、まずはフィールド数を調べたいところです。
レコード数は要素数と同じになっているはずです。

フィールド数を調べたら、各フィールドの種別を調べます。
Shapelibではフィールドの種別はenumで定義されています。

typedef enum {
  FTString,
  FTInteger,
  FTDouble,
  FTLogical,
  FTInvalid
} DBFFieldType;

フィールド名はNULL文字を入れて11byteまでとなっています。

フィールド情報を調べるコードは、以下のようになるでしょうか。

	int nField;
	DBFFieldType eType;
	char szTitle[11];
	int nWidth, nDecimals;
	int i;
	char *pszTypeName;

	nField = DBFGetFieldCount( hDBF );
	for ( i = 0; i < nField; i++ )
	{
		eType = DBFGetFieldInfo( hDBF, i, szTitle, &nWidth, &nDecimals );
		switch ( eType )
		{
		case FTString:
			pszTypeName = "String";
			break;
		case FTInteger:
			pszTypeName = "Integer";
			break;
		case FTDouble:
			pszTypeName = "Double";
			break;
		case FTLogical:
			pszTypeName = "Logical";
			break;
		default:
			pszTypeName = "Invalid";
			break;
		}

		printf( "Field %d: Type = %s, Title = %s, Width = %d, Decimals = %dn",
			i, pszTypeName, szTitle, nWidth, nDecimals );
	}

続いて、レコードを読み込みます。
レコードの順番は図形要素の順番と同じであることとなっています。
なので、図形要素の読み込みと同時にレコードを読み込む必要があります。

また、フィールドの型によってレコードの読み込みで使用する関数が異なります。

上記の図形データの読み込みと併せると、大体以下のようになるでしょうか。

	for ( i = 0; i < nEntities; i++ )
	{
		psElem = SHPReadObject( hSHP, i );

		padfX = new double[psElem->nVertices];
		padfY = new double[psElem->nVertices];
		padfZ = new double[psElem->nVertices];
		for ( j = 0; j < psElem->nVertices; j++ )
		{
			padfX[j] = psElem->padfX[j];
			padfY[j] = psElem->padfY[j];
			padfZ[j] = psElem->padfZ[j];
		}

		for ( j = 0; j < nField; j++ )
		{
			eType = DBFGetFieldInfo( hDBF, j, szTitle, &nWidth, &nDecimals );
			switch ( eType )
			{
			case FTString:
				printf( "%s : %s ", szTitle, DBFReadStringAttribute( hDBF, i, j ) );
				break;
			case FTInteger:
				printf( "%s : %d ", szTitle, DBFReadIntegerAttribute( hDBF, i, j ) );
				break;
			case FTDouble:
				printf( "%s : %f ", szTitle, DBFReadDoubleAttribute( hDBF, i, j ) );
				break;
			default:
				break;
			}
		}
		
		// 使い終わったら
		delete[] padfX;
		delete[] padfY;
		delete[] padfZ;
		SHPDestroyObject( psElem );

		printf( "n" );
	}

最後に、shpファイルとdbfファイルを閉じます。

	SHPClose( hSHP );
	DBFClose( hDBF );
アーカイブ