1.开发环境说明
- QGIS 3.26.2
- Qt 5.14.1
- Visual Studio 2019
2.现象及解决方法
2.1. 现象1及解决方法
通过CMake将QGIS源码生成VS2019工程到build目录下,目录结构如下(图片请单击放大看):
图1
其中build目录是利用CMake工具设置的生成VS工程、编译时的输出目录 ,如下蓝色框所示(图片请单击放大看):
图2
构建QGIS官方源码以Release版编译输出的目录如下:
图3
利用QGIS的头文件及lib库等,自己新建工程以构建符合自己业务的GIS项目,当将工程主程序生成到自己设定的目录而不是上面图3中的QGIS\build\output\bin\Release目录,则某些模块如provider_wms.dll模块中的代码不能进入断点,即断点无效。主程序调用provider_wms.dll模块中的某些函数,但这些函数的功能没能被体现出来,跟没调用该函数一样;但如果把自己的主程序生成输出目录设置为图3中的QGIS\build\output\bin\Release目录,则没有该问题。造成这种问题的原因往往是:主程序调用的库不是预想的库,即被调用的库不是最新的,或不是主程序链入的。
如下(图片请单击放大看):
图4
在上图第73行代码:
pLayer->extent();
返回图层占据范围,结果为:
图5
其实是一个类型为QgsRectangle 类的矩形最上角、最下角的顶点横纵坐标值,而利用QGIS 3.26.2的源码启动QGIS官方的QGIS 3.26.2.exe此值默认返回如下结果:
图6
而图4中的代码是从QGIS官方例子中摘抄过来的,对底层QGIS官方的代码根本就没动过。为何自己原模原样摘抄的代码图层返回的extent不是图6中的数值呢?当将图4代码生成的可执行文件输出到图3中的QGIS\build\output\bin\Release目录时,图5和图6结果是一样的;如果不生成到图3中的QGIS\build\output\bin\Release目录,则结果不一样。 这在某些情况下,会产生某些问题,如:使图层居中显示。
通过断点,跟踪到底层src/core/providers/qgsproviderregistry.cpp中的如下代码:
图7
图7红色方框中的metadata为QgsWmsProviderMetadata类对象的指针。其在provider_wms.dll模块的src/providers/wms/qgswmsprovider.cpp文件中。然后继续断点,一直出现如下界面,再也无法断点进入源码:
图8
如果将图4代码生成的可执行文件输出到图3中的QGIS\build\output\bin\Release目录时,则可以继续断点,不存在图8的问题。当输出到其它目录,则出现图8无法断点进入源码的问题,且上面说的extent()返回不正确的值,因为无法断点就无法找出原因。后来通过QGIS源码分析,发现QgsApplication的resolvePkgPath函数:
QString QgsApplication::resolvePkgPath()
{
..... // 其它代码略,请自行查看代码
for ( const QString &path : paths )
{
f.setFileName( prefix + path + "/qgisbuildpath.txt" );
if ( f.exists() )
break;
}
if ( f.exists() && f.open( QIODevice::ReadOnly ) )
{
ABISYM( mRunningFromBuildDir ) = true;
*sBuildSourcePath() = f.readLine().trimmed();
*sBuildOutputPath() = f.readLine().trimmed();
QgsDebugMsgLevel( QStringLiteral( "Running from build directory!" ), 4 );
QgsDebugMsgLevel( QStringLiteral( "- source directory: %1" ).arg( sBuildSourcePath()->toUtf8().constData() ), 4 );
QgsDebugMsgLevel( QStringLiteral( "- output directory of the build: %1" ).arg( sBuildOutputPath()->toUtf8().constData() ), 4 );
#if defined(_MSC_VER) && !defined(USING_NMAKE) && !defined(USING_NINJA)
*sCfgIntDir() = prefix.split( '/', QString::SkipEmptyParts ).last();
qDebug( "- cfg: %s", sCfgIntDir()->toUtf8().constData() );
#endif
..... // 其它代码略,请自行查看代码
}
图9
第10行:该行就是检查主程序所在目录的父目录下是否存在qgisbuildpath.txt文件。在用CMake工具将QGIS官方源码转为VS工程的输出文件夹中就存在qgisbuildpath.txt文件,如下是我本机下的qgisbuildpath.txt文件:
图10
打开qgisbuildpath.txt文件,可以看到就两行,如下:
图11
其中第1行就是QGIS源码所在目录,第2行就是主程序输出目录,分别对应上述图9代码的13、14 行。即程序启动时,会将qgisbuildpath.txt文件第1行赋值给*sBuildSourcePath(),第2行赋值给 *sBuildOutputPath()。更改qgisbuildpath.txt文件的第2行,将其改为自己主程序的输出目录的父目录,然后启动VS,就可以断点进入到provider_wms.dll模块源码,即能进入到图7红色方框所示的函数代码里面,且图层的extent()也返回正确值。
总结:
如果要使自己编写的主程序输出到自己设定目录下,或遇到断点调试某些dll不能进入源码时,则在主程序输出目录的父目录下手动创建qgisbuildpath.txt文件,并在该文件加入两行,第1行要写入的内容,请参考《QGIS二次开发:构造QgsMapCanvas类对象导致闪退问题解决》;第2行就是自己编写的主程序输出目录的父目录。如下为自己的工程ExquisiteGIS编写的qgisbuildpath.txt文件
图12
而自己的工程ExquisiteGIS的主程序ExquisiteGIS输出目录为:D:\work\GIS15Suo\code\ExquisiteGIS\x64\Release
2.2. 现象2及解决方法
QByteArray CMyGIS::toLatin1_helper(const QString& string)
{
if (string.isEmpty())
return string.isNull() ? QByteArray() : QByteArray("");
return string.toLatin1();
}
// 加载瓦片数据
void CMyGIS::addRasterTileLayer()
{
QgsMapLayerFactory::LayerOptions options(QgsProject::instance()->transformContext());
options.loadDefaultStyle = false;
// 地图服务的URL
auto uri = QString("http://127.0.0.1:8088/{z}/{x}/{y}.png");
// 设置一些参数
QUrlQuery newUrl;
newUrl.addQueryItem(QStringLiteral("type"), "xyz");
newUrl.addQueryItem(QStringLiteral("url"), uri);
newUrl.addQueryItem(QStringLiteral("zmin"), "1"); // 最小精度1
newUrl.addQueryItem(QStringLiteral("zmax"), "12");// 最小精度12
toLatin1_helper(newUrl.toString(QUrl::FullyEncoded));
uri = newUrl.toString(QUrl::FullyEncoded); // 注意:需要转码一下,否则加载不会成功
auto name = "mapservice";
auto type = QgsMapLayerType::RasterLayer;
auto providerKey = "wms";
auto pLayer = qobject_cast<QgsRasterLayer*>(QgsMapLayerFactory::createLayer(uri, name, type, options, providerKey));
if ((nullptr == pLayer) || !pLayer->isValid())
{
Q_ASSERT(0);
return;
}
m_pMapCanvas->setExtent(pLayer->extent());
m_lstLayers.append(pLayer);
m_pMapCanvas->setLayers(m_lstLayers);
m_pMapCanvas->zoomToFullExtent();
m_pMapCanvas->refresh();
}
上面代码向地图服务方请求XYZ Tiles形式的瓦片地图,但代码第34行,返回无效图层,即图层的isValid()返回无效。
解决方法:
请求XYZ Tiles形式的瓦片地图时,用到了provider_wms.dll插件。provider_wms.dll和主程序所在目录必须构成如下目录结构,否则会找不到该dll而导致图层无效而返回。
如果没有plugins目录,请新建。否则上述代码第34行因为找不到provider_wms.dll而导致图层无效而返回。生成的exe存放目录最好不要含有中文,在测试中发现,中文路径会导致第34行返回图层无效。
如下为QGIS的第三方插件:
图13
为了保险起见,将上图所有QGIS插件都放到plugins目录下。