写在前面
静态的插件参数
前面已经讲到,我们在QML端只有Map对象,各种各样的地图源是靠配置LBS插件来实现的;在配置插件的同时,还可以配置插件参数PluginParameter,官方LBS插件能够配置的插件参数参见Plugin References and Parameters。以官方的Open Street Map Plugin为例:
Plugin {
name: "osm"
PluginParameter { name: "osm.useragent"; value: "My great Qt OSM application" }
PluginParameter { name: "osm.mapping.host"; value: "http://osm.tile.server.address/" }
PluginParameter { name: "osm.mapping.copyright"; value: "All mine" }
PluginParameter { name: "osm.routing.host"; value: "http://osrm.server.address/viaroute" }
PluginParameter { name: "osm.geocoding.host"; value: "http://geocoding.server.address" }
}
除了可以为LBS插件配置一些代理的参数以外,比较常用的就是动态缓存目录osm.mapping.cache.directory和离线缓存目录osm.mapping.offline.directory的配置,离线缓存目录的参数配置目前只看到OSM插件才有。
在QML文档中配置了插件参数之后,LBS工厂都会将其打包成一个QVariantMap类型的parameters,在后端用来构造QGeoTiledMappingManagerEngine及其派生类型。所以,自然而然的,我们实现自己的LBS插件的时候,也可以定义从前端的QML文档配置的插件参数,然后在继承QGeoTiledMappingManagerEngine实现的派生类型的构造函数中,将这些参数的键值对解析出来,进行相应的操作。例如在QGeoTiledMappingManagerEngineOsm的构造函数中,我们可以看到如下解析并配置缓存目录的代码:
/* TILE CACHE */
if (parameters.contains(QStringLiteral("osm.mapping.cache.directory")))
{
m_cacheDirectory = parameters.value(QStringLiteral("osm.mapping.cache.directory")).toString();
}
else
{
// managerName() is not yet set, we have to hardcode the plugin name below
m_cacheDirectory = QAbstractGeoTileCache::baseLocationCacheDirectory() + QLatin1String(pluginName);
}
if (parameters.contains(QStringLiteral("osm.mapping.offline.directory")))
m_offlineDirectory = parameters.value(QStringLiteral("osm.mapping.offline.directory")).toString();
QGeoFileTileCacheOsm *tileCache = new QGeoFileTileCacheOsm(m_providers, m_offlineDirectory, m_cacheDirectory);
动态的地图参数
动态的地图参数,在Qt 5.9中作为技术预览,到Qt Location 5.11的时候MapParameter已经改名为DynamicParameter了。MapParameter是通过Map对象的JS接口方法addMapParameter进行添加的,添加的地图参数集合可以通过Map对象的mapParameters属性访问到,例如:
Map {
id: map
plugin: "xxx"
MapParameter {
id: mapParameter
type: "xxx"
}
onMapReadyChanged: map.addMapParameter(mapParameter)
}
利用动态的地图参数特性,我们可以在自己实现的LBS插件上,额外扩展一些前后端的交互功能,例如地图瓦片的离线缓存功能。
实现地图离线缓存
我们在使用地图的时候常常有一些离线场景,比如无人机在野外作业时地面站软件需要离线地图,这时就需要实现地图瓦片数据的离线缓存管理。
一、利用静态的插件参数进行默认的配置
我们在实例化地图插件的时候,可以允许前端通过“[xxx].mapping.offline.directory”参数,对地图的离线缓存目录进行配置,其对应在后端QGeoTiledMappingManagerEngine的派生类构造函数中解析:
// Parse cache offline directory from parameter.
if(parameters.contains(QStringLiteral("[xxx].mapping.offline.directory")))
{
m_offlineDirectory = parameters.value(
QStringLiteral("[xxx].mapping.offline.directory")).toString();
}
else // Set default offline directory
{
QString oldLocation = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
QString newLocation = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
m_offlineDirectory = m_cacheDirectory.replace(oldLocation, newLocation);
}
二、利用动态的地图参数进行前后端交互
我们需要指定一个地理范围(例如通过按住鼠标左键在地图上框选出一个BoundingBox),然后指定瓦片地图的缩放级别(例如弹出表单让用户填写1-10级的ZoomRange),这时我们还需要点击“OK”按钮去执行离线缓存Task,那么这些参数和执行动作的触发信息要如何传递到后端呢?这就需要使用addMapParameter来帮助我们了。
QML的对象是支持属性扩展的(事实上QObject都支持属性扩展 -_-|| ),我们可以对一个MapParameter的具体实例进行扩展,例如:
MapParameter {
id: offlineCache
type: "[xxx].mapping.offline.settings"
property string directory: "[your offline cache directory]"
property var boundingBox: QtPositioning.rectangle(
QtPositioning.coordinate("[top left corner]"),
QtPositioning.coordinate("[bottom right corner]"))
property int minZoom: 0
property int maxZoom: 10
}
我们在后端继承QGeoTiledMapPrivate实现QGeoTiledMap[XXX]Private类型时,就可在addMapParameter方法里解析该参数,并具体实现离线缓存功能了:
void addParameter(QGeoMapParameter* param) override
{
Q_Q(QGeoTiledMap[XXX]);
static const QStringList acceptedParameterTypes = QStringList()
<< QStringLiteral("[xxx].mapping.offline.settings");
QVariantMap kvm = param->toVariantMap();
switch (acceptedParameterTypes.indexOf(param->type())) {
default:
{
qWarning() << "[XXX]Map: Invalid value for property 'type' " + param->type();
break;
}
case 0:
{
QString directory;
QVector<int> zoomRange;
QGeoRectangle boundingBox;
// Parse the "[xxx].mapping.offline.settings" parameters.
if(kvm.contains("directory"))
directory = kvm["directory"].toString();
else
qWarning() << "[XXX]Map: Can not catch the offline cache directory.";
if(kvm.contains("boundingBox"))
boundingBox = kvm["boundingBox"].value<QGeoRectangle>();
else
qWarning() << "[XXX]Map: Can not catch the bounding box.";
if(kvm.contains("minZoom") && kvm.contains("maxZoom"))
{
bool minZoomOk = false, maxZoomOk = false;
int minZoom = qMax(m_minZoomLevel, kvm["minZoom"].toInt(&minZoomOk));
int maxZoom = qMin(m_maxZoomLevel, kvm["maxZoom"].toInt(&maxZoomOk));
if(minZoomOk && maxZoomOk)
{
for(int zIt = minZoom; zIt <= maxZoom; zIt++)
zoomRange.append(zIt);
}
else
qWarning() << "[XXX]Map: Can not catch the zooms when fetch offline cache.";
// To call the fetch offline cache function.
q->fetchOfflineCache(boundingBox, zoomRange, directory);
}
break;
}
}
当前端将该地图参数动态地加入到地图中的时候,就会触发上述方法,并执行到我们具体实现的fetchOfflineCache;当然我们还可以在removeParameter方法中具体实现cancelOfflineCache。
三、模仿瓦片文件缓存机制实现离线缓存
实现fetchOfflineCache的具体思路是:(1)遍历每一个需要离线缓存的Zoom级别,利用QGeoCameraTiles计算出所需的瓦片参数队列;(2)检查已经存在于文件缓存中的瓦片参数,组成需要从文件缓存中复制的队列,其余的瓦片参数组成需要从网络下载的队列;(3)遍历需要从文件缓存中复制的队列,将瓦片复制到离线缓存目录;(4)启动一个计时器,去依次请求下载队列里的瓦片,下载成功后存放到离线缓存目录。
// fetchOfflineCache function : -------------------------------------------------------------------
// Get camera data
QGeoCameraData cameraData = d->m_visibleTiles->cameraData();
QGeoCoordinate center = boundingBox.center();
cameraData.setCenter(center);
// Create camera tiles factory
QGeoCameraTiles cameraTiles;
cameraTiles.setMapType(d->m_visibleTiles->activeMapType()); // 1
cameraTiles.setTileSize(d->m_visibleTiles->tileSize()); // 2
cameraTiles.setMapVersion(d->m_tileEngine->tileVersion()); // 3
cameraTiles.setViewExpansion(1.0); // 4
QString pluginString(d->m_engine->managerName()
+ QLatin1Char('_')
+ QString::number(d->m_engine->managerVersion()));
cameraTiles.setPluginString(pluginString); // 5
// Prepare queues to fetch tiles
QList<QGeoTileSpec> createCopyQueue;
for(int i = 0; i < zoomRange.length(); i++)
{
int currentIntZoom = zoomRange.at(i);
cameraData.setZoomLevel(currentIntZoom);
QGeoProjectionWebMercator projection;
projection.setCameraData(cameraData, true);
QPointF topLeft = projection.coordinateToItemPosition(boundingBox.topLeft(), false).toPointF();
QPointF bottomRight = projection.coordinateToItemPosition(boundingBox.bottomRight(), false).toPointF();
QRectF selectRect = QRectF(topLeft, bottomRight);
QSize selectSize = selectRect.size().toSize();
if(selectSize.isNull()) selectSize = QSize(1, 1); // At least one pixel!
cameraTiles.setScreenSize(selectSize); // 6
cameraTiles.setCameraData(cameraData); // 7
// Create all tiles
QSet<QGeoTileSpec> tiles = cameraTiles.createTiles();
// Check tiles in cache directory
for(const QGeoTileSpec& tile : qAsConst(tiles))
{
if(!d->m_tileCache->offlineStorageContains(tile)) // don't in offline cache
{
if(d->m_tileCache->diskCacheContains(tile))
createCopyQueue << tile; // copy queue from disk cache to offline cache
else
d->m_downloadQueue << tile; // add to download queue
}
}
}
d->m_progressTotal = d->m_downloadQueue.count() + createCopyQueue.count();
if(d->m_downloadQueue.count() == 0 && createCopyQueue.count() == 0)
{
int percentage = 100;
QString message = QStringLiteral("All tiles needed to fetch have been in offline storage.");
emit fetchTilesProgressChanged(percentage, message);
return;
}
// Copy tiles from cache directory to offline directory
for(const QGeoTileSpec& tile : qAsConst(createCopyQueue))
{
int percentage = 100 * (++d->m_progressValue) / d->m_progressTotal;
const QString tileName = tileSpecToName(tile);
QString message;
if(d->m_tileCache->addToOfflineStorage(tile))
message = QString("Fetched the %1 from disk cache successfully.").arg(tileName);
else
message = QString("Fetched the %1 from disk cache unsuccessfully.").arg(tileName);
emit fetchTilesProgressChanged(percentage, message);
}
// Start a timer to request tiles online
if(!d->m_downloadQueue.isEmpty() && !d->m_timer.isActive())
d->m_timer.start(0, this);
// timerEvent function: ---------------------------------------------------------------------------
Q_D(QGeoTiledMap[XXX]);
if (event->timerId() != d->m_timer.timerId())
{
QObject::timerEvent(event);
return;
}
QMutexLocker ml(&d->m_queueMutex);
if (d->m_downloadQueue.isEmpty())
{
d->m_timer.stop();
return;
}
ml.unlock();
// request the next tile
requestNextTile();
其中从网络下载瓦片的部分,参考QGeoTileFetcher进行实现。
来源:oschina
链接:https://my.oschina.net/HangXiaoZhuOSZone/blog/3208763