本文介绍如何读写shp文件的矢量线。shape文件包括一个主文件,一个索引文件,和一个dBASE表,其中主文件的后缀就是.shp,图形索引文件的后缀是.shx,dBASE表的后缀是.dbf,每个图形的属性数据存储在dBase格式的数据表之中。其它的文件还包括:Shapefile空间索引格式(.sbn),这是一个二进制的空间索引文件,仅仅可以应用在ESRI的软件之中。其文件格式没有公开的文档,其他厂商也没有实现这个文件。.sbn并不是必须的,因为.shp文件之中已经包含了所有的解析空间数据所需的信息。
在项目中,我们经常需要提取一些道路标线等对象保存为shp文件。本文使用到了Eigen库,需要的同学自己下载使用。
使用GDAL读写shp需要注意以下的问题:
(1)为了支持中文路径,在注册了驱动之后,加上第三句就可以了。必须设置为“NO”。
GDALAllRegister();
OGRAllRegister();
CPLSetConfigOption("GDAL_FILENAME_IS_UTF8","NO");
(2)为了支持Shp中的中文属性输入,在注册了驱动之后,也得加上一句。
CPLSetConfigOption( "SHAPE_ENCODING", "" );
有时候,也可以指定函数第二个选项为“CP936”
CPLSetConfigOption("SHAPE_ENCODING","CP936");
(3)使用double或者float类型作为属性值输入,需要设置字段的宽度:
// create the attributes tables:
OGRFieldDefn poFieldID("ID", OFTInteger);
OGRFieldDefn oFieldx("x坐标", OFTReal);
OGRFieldDefn oFieldy("y坐标", OFTReal);
OGRFieldDefn oFieldz("z坐标", OFTReal);
poFieldID.SetWidth(40);
oFieldx.SetPrecision(8);
oFieldy.SetPrecision(8);
oFieldz.SetPrecision(8);
(4)版本问题:在GDAL 2.0+的C/C++版本中移除了对于OGRDatasource及OGRSFDriver的支持。分别用GDALDataset、GDALDriver代替,类下的方法不变。
GDAL2.0以下版本读取矢量线shp文件:
bool readPolylineShp(const std::string &filename, std::vector<std::vector<Eigen::Vector3f>> &pts)
{
// if this file exist, else exit!
if((_access(filename.c_str(), 0)) != -1)
{
std::cout << "This .shp file exist." << filename << std::endl;
//注册所有的文件格式驱动,这个函数注册了GDAL/OGR支持的所有格式
OGRRegisterAll();
//下一步我们将打开输入的OGR数据文件。数据文件可以是文件,关系型数据库,文件路径,甚至可能是远程的网络服务,这点取决于我们使用的驱动。但是,数据源的名字通常只是一个简单的字符串。既然这样拿我们就编写一个打开shapefile的程序。第二个参数(FLALSE)告诉OGRSFDriverRegistrar::Open() 函数我们不需要update access。如果失败返回NULL,并报错。
OGRDataSource *poDS;
poDS = OGRSFDriverRegistrar::Open(filename.c_str(), TRUE);
if(poDS == NULL)
{
std::cout << "Failed in opening adding shp file;" << std::endl;
OGRDataSource::DestroyDataSource(poDS);
exit(1);
}
//一个OGRDataSource可能包含很多的层。所包含层的数量我们可以用过调 OGRDataSource::GetLayerCount()得到,并且其中每一个曾我们利用索引调用OGRDataSource::GetLayer()得到。也可以利用层的名字得到。
int layerN = poDS->GetLayerCount();
//std::cout << "layer count is: " << layerN << std::endl;
OGRLayer *poLayer;
poLayer = poDS->GetLayer(0);
if(poLayer == NULL)
{
std::cout << "Failed in getting layer in existing .shp file" << std::endl;
OGRDataSource::DestroyDataSource(poDS);
return false;
}
// get vertexes on lines
std::vector<std::vector<Eigen::Vector3f>>().swap(pts);
//自从我们开始fresh with这个层,就没有这么严格了。很明智地我们需要调用Layer::ResetReading()来确保我们是从层的开头开始。
poLayer->ResetReading();
// 现在我们开始读取层里面的features。在开始之前我们需要指定一个attribute或者spatial filter来严格控制我们得到的feature。不过现在我们只是得到所有的features。
OGRSpatialReference *shp_spf;
shp_spf = poLayer->GetSpatialRef(); // geographical projection information
//
int iFeatureCount = poLayer->GetFeatureCount();
pts.resize(iFeatureCount);
OGRFeature *poFeature=NULL;
// traverse all features in the layer:我们不断地调用OGRLayer::GetNextFeature()函数来遍历所有的features,当遍历完所有的features后返回NULL。
int flag_line = 0;
while ((poFeature = poLayer->GetNextFeature()) != NULL)
{
//feature里面提取出几何(geometry)数据
OGRGeometry *poGeometry = poFeature->GetGeometryRef();
OGRwkbGeometryType geotype;
geotype = poGeometry->getGeometryType();
OGRPoint *poPoint = NULL;
OGRLineString *poLine = NULL;
//确定这个几何数据的类型,如果是点,我们将他标为点并且进行操作,如果是其他的内省我们write占位符。这里的类型是多段线
if(wkbLineString25D == geotype)
{
poLine = (OGRLineString*)poGeometry;
int pNUM = poLine->getNumPoints();
for(int i = 0; i < pNUM; ++i)
{
Eigen::Vector3f temp_pt;
temp_pt[0] = poLine->getX(i);
temp_pt[1] = poLine->getY(i);
temp_pt[2] = poLine->getZ(i);
pts[flag_line].push_back(temp_pt);
}
}
flag_line++;
delete poPoint;
delete poLine;
}
//资源清理
GDALClose(poDS);
return true;
}
else
{
std::cout << "This .shp file is not exist!" << std::endl;
return false;
}
}
GDAL2.0以下版本写矢量线shp文件:
bool WriteShp(string filename)
{
//创建
const char *pszDriverName = "ESRI Shapefile";
OGRSFDriver *poDriver;
OGRRegisterAll();
poDriver = OGRSFDriverRegistrar::GetRegistrar()->GetDriverByName(pszDriverName);
if (poDriver == NULL)
{
std::cout << pszDriverName << " : This driver is not available!" << std::endl;
exit(1);
}
OGRDataSource* poDS;
if ((_access(filename.c_str(), 0)) != -1)
{
std::cout << "Exist this shp file." << std::endl;
remove(filename.c_str());
}
poDS = poDriver->CreateDataSource(filename.c_str(), NULL);
if (poDS == NULL)
{
std::cout << "Failed in creating layers" << std::endl;
exit(1);
}
// create layers:
OGRLayer *poLayer;
//poLayer = poDS->CreateLayer("curbs3D", NULL, wkbMultiLineString25D, NULL);
poLayer = poDS->CreateLayer("point_out", NULL, wkbPoint, NULL);
if (poLayer == NULL)
{
std::cout << "Layer creation failed!" << std::endl;
exit(1);
}
// create the attributes tables:
OGRFieldDefn poFieldID("ID", OFTInteger);
poFieldID.SetWidth(40);
if (poLayer->CreateField(&poFieldID) != OGRERR_NONE)
{
std::cout << "ID field creation failed!" << std::endl;
exit(1);
}
OGRFieldDefn oField("Name", OFTString);
oField.SetWidth(32);
if (poLayer->CreateField(&oField) != OGRERR_NONE)
{
printf("Creating Name field failed.\n");
exit(1);
}
//下面从stdin中循环读取 “x,y,name”格式的点信息。
double x = 0, y = 100;
char szName[33];
for (int a = 0; a != 10; ++a)
{
x += a;
y += a;
OGRFeature *poFeature;
poFeature = new OGRFeature(poLayer->GetLayerDefn());
poFeature->SetField("Name", szName);
OGRPoint pt;
pt.setX(x);
pt.setY(y);
poFeature->SetGeometry(&pt);
if (poLayer->CreateFeature(poFeature) != OGRERR_NONE)
{
cout << "创建要素失败" << endl;
}
//OGRFeature::DestroyFeature(*poFeature);//创建一个feature.OGRLayer::CreateFeature() 只是重新复制了一个feature,因此操作完成后需要清除feature对象
}
OGRDataSource::DestroyDataSource(poDS);
return true;
}