前言
相信大家在使用QT开发中难免会需要用到地图开发,目前主流是使用QWebEngineView调用浏览器,使用浏览器加载第三方api,如openlayers 和谷歌地图API等。但是这种优点是api支持度高非常简单,容易上手。但是缺点是没法缓冲地图资源,必须依赖地图服务器。
还有一种小众的解决方法是使用Qt自带的QtLocation(如果你的程序不准备使用qml为主的话,你基本就不需要往下看了),这篇文章就是介绍QtLocation的使用方法的。
openlayers 使用方法 :https://blog.youkuaiyun.com/luck_anan/article/details/129662854
需求
1.支持离线地图、在线天地图和本地缓冲地图
2.可以导入导出缓冲的在线资源
流程图
我认为讲半天废话,不放流程图的都是耍流氓
程序实现
插件编写
缓冲实现
1.这里实现了缓冲入库
class stMapTileData : public QObject
{
Q_OBJECT
public:
//瓦片数据
quint32 hash; //瓦片图块位置Hash
QString format; //图片格式
QByteArray byte; //瓦片图片
int x;
int y;
int zoom; //地图层级
quint32 mapID; //地图ID
};
void CMapGeoFileTileCache::addToSqlite(const QGeoTileSpec &spec, const QString &format, const QByteArray &bytes)
{
QSharedPointer<stMapTileData> tile = QSharedPointer<stMapTileData>(new stMapTileData);
tile->hash = CMapLoadSetting::getTileHash(spec.mapId(), spec.x(), spec.y(), spec.zoom());
tile->format = format;//图片格式
tile->byte = bytes; //瓦片图片
tile->x = spec.x();//x
tile->y = spec.y();//y
tile->zoom = spec.zoom();//地图层级
tile->mapID = spec.mapId();//地图ID
//不阻塞,这里主要是写入速度太慢
QMetaObject::invokeMethod(CMapEngineMgr::Instance()->getSqlThread().data(), "slot_writeSql",
Qt::QueuedConnection, Q_ARG(QSharedPointer<stMapTileData>, tile));
}
void CSqlDataWorker::slot_writeSql()
{
if (m_pQuery && !m_queue.isEmpty()){
auto tile = m_queue.dequeue();
//如果不存在就插入,存在就更新的方式;
m_pQuery->prepare(
"INSERT INTO Tiles(hash, format, tile, size, x, y, zoom, mapID, dateTime) VALUES(?, "
"?, ?, ?, ?, ?, ?, ?, ?)");
m_pQuery->addBindValue(tile->hash);
m_pQuery->addBindValue(tile->format);
m_pQuery->addBindValue(tile->byte);
m_pQuery->addBindValue(tile->byte.size());
m_pQuery->addBindValue(tile->x);
m_pQuery->addBindValue(tile->y);
m_pQuery->addBindValue(tile->zoom);
m_pQuery->addBindValue(tile->mapID);
m_pQuery->addBindValue(QDateTime::currentDateTime().toMSecsSinceEpoch());
if (m_pQuery->exec()) {
//新的数据已存入
} else {
// Map数据已存在于数据库
}
m_pQuery->finish();
}
}
2.这里实现是缓冲读取
QSharedPointer<QGeoTileTexture> CMapGeoFileTileCache::getFromSqlite(const QGeoTileSpec &spec)
{
QSharedPointer<stMapTileData> tile(new stMapTileData) ;//创建一个瓦片
//获取Hash
tile->hash = CMapLoadSetting::getTileHash(spec.mapId(), spec.x(), spec.y(), spec.zoom());
auto start = clock();
//从数据库读取瓦片数据
//阻塞等待返回,因为读取数据非常快,阻塞没有影响
QMetaObject::invokeMethod(CMapEngineMgr::Instance()->getSqlThread().data(), "slot_readTile",
Qt::BlockingQueuedConnection, Q_ARG(QSharedPointer<stMapTileData>, tile));
qDebug() << "read time:" << clock() - start << "ms";
if (tile->byte.isEmpty())
return QSharedPointer<QGeoTileTexture>();
QImage image;
//一些来自服务器的瓷砖可能是有效的图像,但瓷砖获取器
//也许可以将它们识别为不应该显示的方块。
//如果是这种情况,瓷砖获取器应该在文件中写入“NoRetry”。
if (isTileBogus(tile->byte)) {
QSharedPointer<QGeoTileTexture> tt(new QGeoTileTexture);
tt->spec = spec;
tt->image = image;
return tt;
}
// 这是一个真正无效的图像。获取器应该再试一次。
if (!image.loadFromData(tile->byte/*,tile->format.toLatin1()*/)) {
handleError(spec, QStringLiteral("瓦片图像有问题"));
return QSharedPointer<QGeoTileTexture>(0);
}
// 在这里转换它,而不是在每个QSGTexture::bind()中
if (image.format() != QImage::Format_RGB32 && image.format() != QImage::Format_ARGB32_Premultiplied)
image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
addToMemoryCache(spec, tile->byte, tile->format);//添加到内存缓冲
QSharedPointer<QGeoTileTexture> tt = addToTextureCache(spec, image);//添加瓦片到缓冲
if (tt) {
return tt;
}
return QSharedPointer<QGeoTileTexture>();
}
多地图加载
CMapUrlEngineMgr::CMapUrlEngineMgr(QObject *parent)
: QObject{parent}
{
CMapProviderBase * pMapBase = new CMapProviderTianDi(QGeoMapType::SatelliteMapDay,this); //天地图卫星图
m_hashProvides["Tianditu Satellite"] =pMapBase;
pMapBase->setLicense( CMapLoadSetting::Instance()->m_mapMapData["Tianditu Satellite"].strLicense);
pMapBase->setFormat( CMapLoadSetting::Instance()->m_mapMapData["Tianditu Satellite"].strFormat);
//m_hashProvides["Tianditu Street"] = new TiTiandituCiaMapProvider(this); //天地图路网标注信息图
pMapBase = new CMapProviderOffLine(QGeoMapType::SatelliteMapDay,this); //离线卫星图
m_hashProvides["OffLine Satellite"] = pMapBase;
pMapBase->setLicense( CMapLoadSetting::Instance()->m_mapMapData["OffLine Satellite"].strLicense);
pMapBase->setFormat( CMapLoadSetting::Instance()->m_mapMapData["OffLine Satellite"].strFormat);
}
CMapGeoTiledMappingManagerEngine::CMapGeoTiledMappingManagerEngine(const QVariantMap ¶meters, QGeoServiceProvider::Error *error, QString *errorString)
:QGeoTiledMappingManagerEngine()
{
//设置图层
QGeoCameraCapabilities cameraCaps;
cameraCaps.setMinimumZoomLevel(2.0);//最大缩放
cameraCaps.setMaximumZoomLevel(18.0);//最小缩放 地图层级
cameraCaps.setSupportsBearing(true);
cameraCaps.setSupportsTilting(true);
// cameraCaps.setMinimumTilt(0);
// cameraCaps.setMaximumTilt(80);
// cameraCaps.setMinimumFieldOfView(20.0);
// cameraCaps.setMaximumFieldOfView(120.0);
// cameraCaps.setOverzoomEnabled(true);
setCameraCapabilities(cameraCaps);
//宏注册 QByteArray("TefeiMap") 由Json导入
#define QGCGEOMAPTYPE(a,b,c,d,e,f) QGeoMapType(a,b,c,d,e,f,QByteArray("demoMap"), cameraCaps)
//获取所有的 地图 路径样式 包括 离线和天地图 都从这里获取
QList<QGeoMapType> mapList;
//地图瓦片引擎
auto hashProviders = CMapEngineMgr::Instance()->getUrlEngine()->getProviderTable();
for (auto cIt = hashProviders.cbegin(); cIt != hashProviders.cend(); ++cIt) {
mapList.append(QGCGEOMAPTYPE(cIt.value()->getMapStyle(), cIt.key(), cIt.key(), false, false, CMapEngineMgr::Instance()->getUrlEngine()->getIdFromType(cIt.key())));
}
setSupportedMapTypes(mapList);//设置支持的映射类型
//没有用QGeoFileTileCache,默认加载地缓存不会释放,还没找到原因,估计要分析它的源码
//缓冲路径
QString strCacheDirectory = CMapLoadSetting::Instance()->m_strCacheSqlDir;
//设置地图缓冲,如果不需要缓存,可设置setMaxDiskUsage 大小,限制到小内存,,
//无法 注释setTileCache(); 注释后会默认缓冲图片
// QGeoFileTileCache* tileCache = new QGeoFileTileCache(cacheDirectory, this);
auto tileCache = new CMapGeoFileTileCache(strCacheDirectory, this);
//auto tileCache = new QGeoFileTileCache(strCacheDirectory, this);
tileCache->setMaxDiskUsage(1024 * 1024);
tileCache->setMaxMemoryUsage(1024 * 1024 * 100);//100m内存
setTileCache(tileCache);
//创建地图瓦片获取器
//从网络中加载瓦片
auto tileFetcher = new CMapGeoTileFetcher(parameters, this);
setTileFetcher(tileFetcher);
//结束
m_prefetchStyle = QGeoTiledMap::PrefetchNeighbourLayer; //QGeoTiledMap::NoPrefetching;//没有预先存取 //可设置缓冲一些瓦片
*error = QGeoServiceProvider::NoError;
errorString->clear();
}
程序加载
int main(int argc, char *argv[])
{
//导入地图插件
Q_IMPORT_PLUGIN(CMapGeoServiceProviderFactory);
}
Map{
id: map
width: parent.width
height: parent.height
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
zoomLevel: MapEngineMgr.getMapZoom().rawValue //缩放等级
center: MapEngineMgr.getMapCenter() //中心点
//gesture.acceptedGestures: MapGestureArea.PinchGesture //支持的手势
plugin: Plugin { name: "demoMap" }//插件名称 对应 MapGeoTiledMappingManagerEngine cpp 第26行 和Json "Provider": "TefeiMap",
visible: true
function updateActiveMapType() {
for (var i = 0; i < map.supportedMapTypes.length; i++) {
console.log("地图供应商",map.supportedMapTypes[i].name)
//在这里可以切换地图
if (fullMapName === map.supportedMapTypes[i].name) {
map.activeMapType = map.supportedMapTypes[i]
return
}
}
}
Component.onCompleted: {//加载完毕后 更新一次界面
updateActiveMapType()
}
}
地图瓦片选择
瓦片选择为 原始瓦片 + 墨卡托投影
最终实现
下载地址
测试程序下载地址
测试程序下载地址:
源码下载地址
优快云:
GitHub:
GitLab:
缺陷
必须使用QML,而且API支持较少,文档较少,比不得openlayers这些成熟的前端地图框架。
最后
本人才疏学浅,还有很多不足,欢迎各位指出,也请收下留情,勿喷。
参考
1.qgroundcontrol地面站:http://qgroundcontrol.com/
2.龚建波 QML QtLocation地图应用学习-5:https://blog.youkuaiyun.com/gongjianbo1992/article/details/103655126
转载请注明出处谢谢。