1.QGIS二次开发包实现
在槽函数中添加代码:
void DataViewer::on_actionBuffer_triggered() //缓冲区
{
QgsProject* project = QgsProject::instance();
QgsMapLayer* firstLayer = project->mapLayers().values().first();
// 将获取到的第一个图层传给pointLayer
QgsVectorLayer* pointLayer = qobject_cast<QgsVectorLayer*>(firstLayer);
// 获取点图层的数据提供程序
QgsVectorDataProvider* dataProvider = pointLayer->dataProvider();
// 获取点图层的字段信息
QgsFields fields = dataProvider->fields();
// 创建一个新的内存图层用于显示缓冲区
QgsVectorLayer* bufferLayer = new QgsVectorLayer("Polygon?crs=EPSG:4326", "Buffer Layer", "memory");
QgsVectorDataProvider* bufferDataProvider = bufferLayer->dataProvider();
// 添加字段到新图层
bufferDataProvider->addAttributes(fields.toList());
// 开始编辑新图层
bufferLayer->startEditing();
// 遍历点图层的所有要素,为每个点生成缓冲区
QgsFeatureIterator featureIterator = pointLayer->getFeatures();
QgsFeatureList bufferFeatures;
QgsFeature pointFeature;
while (featureIterator.nextFeature(pointFeature))
{
QgsGeometry geom = pointFeature.geometry(); //获取几何
QgsGeometry bufferedGeometry = bufferByMeter(geom, 0.1, m_mapCanvas); //0.1是缓冲区的半径,可以根据需要调整
QgsFeature bufferFeature;
bufferFeature.setGeometry(bufferedGeometry);
bufferFeature.setAttributes(pointFeature.attributes());
bufferFeatures.append(bufferFeature);
}
// 将生成的缓冲区要素添加到新图层
bufferDataProvider->addFeatures(bufferFeatures);
// 结束编辑新图层
bufferLayer->commitChanges();
// 将新图层添加到地图
QgsProject::instance()->addMapLayer(bufferLayer);
// 刷新地图视图
m_mapCanvas->refresh();
}
上述代码只能获取当前地图中的第一个图层作为缓冲区分析的数据源,不支持选择图层。
运行结果
面缓冲区:
点缓冲区:
2.GDAL实现
这部分参考一个大佬的代码(https://github.com/Leopard-C/iCGIS),在此基础上自行实现了线图层的缓冲区分析。
我的工程中一共实现了五个分析功能,分别为参考代码中已经实现的空间查询、核密度分析以及我自行实现的缓冲区分析、K均值聚类和矢量面要素的面积计算(其他部分有时间再写)。建立五个类分别实现五种功能,并为每一个功能创建参数设置界面。创建一个抽象命令类和命令管理器,并实现五个具体命令类,使用命令模式管理功能。
创建一个Buffer类,在Buffer.h中添加代码:
#include <QComboBox>
#include <QDialog>
#include <QLineEdit>
#include <QObject>
#include<vector>
#include <gdal/gdal_priv.h>
#include <gdal/cpl_conv.h>
#include <gdal/gdal.h>
/************************ 线缓冲区生成 ************************/
class Buffer : public GeoTool
{
Q_OBJECT
public:
Buffer(QWidget* parent = nullptr);
~Buffer();
OGRLayer* Open(std::string strFilePath); //打开文件
//void createShp(std::string strFilePath, std::string outputPath); //创建输出文件
void generateBuffer(std::string inputFilePath, std::string outputFilePath,int radius); //缓冲区分析
signals:
//添加结果图层到当前地图中
void sigSendLayerToGPU(GeoLayer* layer, bool bUpdate = true);
void sigAddNewLayerToLayersTree(GeoLayer* layer, bool bUpdate = true);
private:
void setupLayout(); //工具界面布局
void initializeFill(); //初始值
public slots:
void onChangeInputFeatures(const QString& name); //改变输入图层
void onSetOutputFile(); //设置输出路径
void onBtnOKClicked(); //OK按钮点击事件
private:
QComboBox* comboInputFeatures; //输入框
QLineEdit* lineEditOutputVector; //输出框
QLineEdit* BufferRadius; //缓冲半径
};
在Buffer.cpp中添加代码:
1.界面设计:在setpuLayout函数中手动编写界面代码。
void Buffer::setupLayout()
{
QVBoxLayout* mainLayout = new QVBoxLayout(this);
QLabel* label1 = new QLabel(tr("Input features")); //输入要素
comboInputFeatures = new QComboBox();
mainLayout->addWidget(label1);
mainLayout->addWidget(comboInputFeatures);
connect(comboInputFeatures, &QComboBox::currentTextChanged,
this, &Buffer::onChangeInputFeatures);
QLabel* label3 = new QLabel(tr("Output file")); //输出要素
lineEditOutputVector = new QLineEdit();
QPushButton* btnSelectFile = new QPushButton();
QHBoxLayout* hLayout1 = new QHBoxLayout();
hLayout1->addWidget(lineEditOutputVector);
hLayout1->addWidget(btnSelectFile); //选择输出文件
mainLayout->addWidget(label3);
mainLayout->addLayout(hLayout1);
QLabel* label5 = new QLabel(tr("radius")); //缓冲半径
BufferRadius = new QLineEdit();
BufferRadius->setAlignment(Qt::AlignRight);
mainLayout->addWidget(label5);
mainLayout->addWidget(BufferRadius);
QPushButton* btnOK = new QPushButton("OK");
QPushButton* btnCancel = new QPushButton(tr("Cancel"));
QSpacerItem* spacerItem1 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
QSpacerItem* spacerItem2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
QSpacerItem* spacerItem3 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
QHBoxLayout* hLayout2 = new QHBoxLayout();
hLayout2->addItem(spacerItem1);
hLayout2->addWidget(btnOK);
hLayout2->addItem(spacerItem2);
hLayout2->addWidget(btnCancel);
hLayout2->addItem(spacerItem3);
mainLayout->addLayout(hLayout2);
btnOK->setFocus();
btnOK->setDefault(true);
//信号和槽
connect(btnSelectFile, &QPushButton::clicked, this, &Buffer::onSetOutputFile); //选择输出文件
connect(btnOK, &QPushButton::clicked, this, &Buffer::onBtnOKClicked);
connect(btnCancel, &QPushButton::clicked, this, &Buffer::close);
}
运行一下查看界面效果:
将图层填充到combox中,选择输入要素,获取输出路径:
//填充默认值(inputFeature)
void Buffer::initializeFill() //默认第一个图层
{
int layersCount = map->getNumLayers();
for (int i = 0; i < layersCount; ++i) {
GeoLayer* layer = map->getLayerById(i);
if (layer->getLayerType() == featureLayer) {
comboInputFeatures->addItem(layer->getName());
}
}
}
//选择输入要素
void Buffer::onChangeInputFeatures(const QString& name)
{
GeoFeatureLayer* layer = map->getLayerByName(name)->toFeatureLayer();
if (layer->getGeometryType() != kPoint) //针对点要素
{
return;
}
}
//输出路径选择
void Buffer::onSetOutputFile()
{
QString filepath = QFileDialog::getSaveFileName(this, tr("Set output shp file"), ".", "SHAPE File(*.shp)");
lineEditOutputVector->setText(filepath);
}
生成缓冲区的核心代码:
// 读取矢量线数据并生成缓冲区
void Buffer::generateBuffer(std::string inputFilePath, std::string outputFilePath, int radius)
{
OGRLayer* poLayer = Open(inputFilePath); //打开数据
OGRFeature* poFeature = poLayer->GetFeature(0);
int nFeatureCount = poLayer->GetFeatureCount(); // 获取输入图层中的要素数量
//OGRLayer* inputLayer = inputDataset->GetLayer(0);
// 创建输出缓冲区的矢量数据文件
GDALDriver* outputDriver = GetGDALDriverManager()->GetDriverByName("ESRI Shapefile");
GDALDataset* outputDataset = outputDriver->Create(outputFilePath.c_str(), 0, 0, 0, GDT_Unknown, nullptr);
OGRLayer* outputLayer = outputDataset->CreateLayer("buffer", nullptr, wkbPolygon, nullptr);
// 获取输入图层中的要素数量
/*int featureCount = inputLayer->GetFeatureCount();*/
// 循环处理每个要素,生成缓冲区
for (int i = 0; i < nFeatureCount; i++)
{
OGRFeature* inputFeature = poLayer->GetFeature(i);
OGRGeometry* inputGeometry = inputFeature->GetGeometryRef();
OGRGeometry* bufferGeometry = inputGeometry->Buffer(radius); // 生成100单位长度的缓冲区
// 创建输出图层的要素
OGRFeature* outputFeature = OGRFeature::CreateFeature(outputLayer->GetLayerDefn());
outputFeature->SetGeometry(bufferGeometry);
outputLayer->CreateFeature(outputFeature);
// 释放资源
OGRFeature::DestroyFeature(inputFeature);
OGRFeature::DestroyFeature(outputFeature);
}
// 关闭数据集
GDALClose(poLayer);
GDALClose(outputDataset);
qDebug() << "Buffer generation completed.";
}
OK按钮响应:
void Buffer::onBtnOKClicked()
{
//输入文件
std::string inputFilePath = "D:\\data\\polyline_2.shp";
std::string outputFilePath = "D:\\data\\bufferLine.shp";
//获取缓冲半径
int radius = 0;
if (!BufferRadius->text().isEmpty())
radius = BufferRadius->text().toDouble();
//输出
QString outputVectorFile = lineEditOutputVector->text();
if (outputVectorFile.isEmpty())
{
QMessageBox::critical(this, "Error", "Ouput file can't be empty");
return;
}
//generateBuffer(inputFilePath, outputFilePath, radius);
BufferCommand* cmd = new BufferCommand(inputFilePath, outputFilePath, radius);
CommandManager* commandManager = new CommandManager();
// 将命令对象传递给命令管理器执行
commandManager->setCommand(cmd);
commandManager->executeCommand();
}
GDAL读取矢量文件:
//打开文件
OGRLayer* Buffer::Open(std::string strFilePath)
{
//GDAL注册
GDALAllRegister();
//解决中文乱码问题
CPLSetConfigOption("GDAL_FILENAME_IS_UTF8", "NO");
//声明数据集指针
GDALDataset* poSrcShp;
//此处填入所需打开矢量文件路径
//数据集指针初始化
poSrcShp = (GDALDataset*)GDALOpenEx(strFilePath.c_str(), GA_Update, NULL, NULL, NULL);
if (poSrcShp == NULL)
{
std::cout << "数据打开失败!" << std::endl;
return NULL;
}
//读取矢量数据,shp数据默认图层数为1
OGRLayer* poLayer;
poLayer = poSrcShp->GetLayer(0);
return poLayer;
}