










想实现这个功能由来已久,对标奥维地图或者qgc等地图控件,都具备这个功能,也就是当瓦片不存在的时候,先查找上级瓦片,然后裁切对应区域,最后拉伸填充到当前瓦片区域,看起来会模糊一些,但总归是效果满足人眼的需要,不然就是一个空白区域很突兀。理论上来说,地图放大的时候,上一级的瓦片之前是肯定加载过的,那就可以利用这个机制做文章,有标准公式来计算,比如当前瓦片区域对应x、y、z,他的上级瓦片是x/2、y/2、z-1,然后再根据xy的奇数偶数,来判断需要裁切四个区域的哪个区域,按照这个机制吭哧吭哧一顿输出,搞完收工非常完美,先从内存瓦片中找到对应上级的瓦片,裁切对应区域拉伸填充占位,再去联网下载瓦片,下载好了再覆盖对应区域,整体上看下来就是先一个模糊的瓦片,下载好了替换后就是一个清晰的图片。
光有这个查找上级的功能还是不够的,顺藤摸瓜能不能实现无极缩放呢?或者最少两到三个级别,观察发现市面上的其他地图软件,几乎都是2-3级别,后面发现有通用的公式,传入xy和层级,直接裁切哪个区域的图片返回,2级别是图片的八分之一,3层级就是16分之一,层级越大切割的数量越多,图片月模糊,又是一顿吭哧吭哧搞,实现了无极缩放,用户非常满意,理论上来说,只要提供一张最初的世界地图瓦片,可以无极缩放到所有层级,后面验证下来,我还是太年轻了,超过8个层级,居然用的原始图片裁切的,原来最大只能往上找8个层级,因为第八层级就已经是256等分了,如果是256的瓦片,就是一个像素要填充一个区域,全是马赛克,再小就是0,导致裁切失败返回的原图。



void LayerMap::draw(QPainter *painter, const RectWorldPx &backbuffer_rect, int cur_zoom) const
{
QReadLocker locker(&m_mutex);
//没有设置或者不可见则不用继续
if (!m_map_adapter || !m_map_image || !isVisible(cur_zoom)) {
return;
}
//没有设置瓦片地址也不用继续
if (m_map_adapter->url().isEmpty()) {
return;
}
//绘制背景颜色/形成样式风格
if (m_bgColor != Qt::transparent) {
painter->fillRect(backbuffer_rect.rawRect(), m_bgColor);
}
int z = cur_zoom;
int size = m_tile_size;
bool offline = m_map_adapter->isOffline();
//计算瓦片绘制区域
int left = qFloor(backbuffer_rect.leftPx() / size);
int top = qFloor(backbuffer_rect.topPx() / size);
int right = qFloor(backbuffer_rect.rightPx() / size);
int bottom = qFloor(backbuffer_rect.bottomPx() / size);
//绘制网格背景/绘制完不用继续
if (ImageHelper::drawTileGrid) {
this->drawGrid(painter, z, size, left, top, right, bottom);
return;
}
//1. 先从内存中查找/找不到则记住XY/意味着需要获取图片
//2. 内存中找不到/逐个上级瓦片电子放大/临时填充区域
//3. 再对XY列表获取图片/本地只查找本地文件/远程只联网获取
//4. 地址队列和标识队列只是为了方便直接传入/不用再计算
//5. scale队列用来存放电子放大的层级/方便下面判断是否找到了电子放大的
//6. image队列用来存放电子放大的图片/下面绘制需要用到/找到了原图则用原图/否则用电子放大的
QList<int> listX;
QList<int> listY;
QList<QUrl> listUrl;
QList<QString> listFlag;
QList<int> listScale;
QList<QPixmap> listImage;
#if 1
//先从内存中查找/从中间往四周扩散/靠近地图焦点的瓦片优先查找和下载
int centerX = (left + right) / 2;
int centerY = (top + bottom) / 2;
//按到中心点的距离分桶/距离近的瓦片优先处理/QMap数据会自动排序
QMap<int, QList<QPoint> > mapTile;
for (int x = left; x <= right; ++x) {
for (int y = top; y <= bottom; ++y) {
int distance = (x - centerX) * (x - centerX) + (y - centerY) * (y - centerY);
mapTile[distance] << QPoint(x, y);
}
}
QMapIterator<int, QList<QPoint> > itTile(mapTile);
while (itTile.hasNext()) {
itTile.next();
foreach (QPoint point, itTile.value()) {
int x = point.x();
int y = point.y();
#else
//先从内存中查找/从左往右从上到下依次查找
for (int x = left; x <= right; ++x) {
for (int y = top; y <= bottom; ++y) {
#endif
if (!m_map_adapter->isTileValid(x, y, z)) {
continue;
}
QUrl url;
QString flag = this->getUrlFlag(url, x, y, z, 0);
QPixmap pixmap = ImageHelper::findPixmap(flag);
if (pixmap.isNull()) {
//循环从内存中的上级瓦片中查找/找到则裁切对应区域/返回放大层级
int scale = this->findParentPixmap(pixmap, x, y, z, size, offline);
//如果还没找到/往下级找四张拼起来/拼接成功则把scale改为255区分上下级
this->findChildPixmap(pixmap, scale, x, y, z, size, offline);
listX << x;
listY << y;
listUrl << url;
listFlag << flag;
listScale << scale;
listImage << pixmap;
} else {
this->drawPixmap(painter, pixmap, size, x, y, 0, offline, false);
}
}
}
//超过最大查找层级不用继续
int level = z - m_tile_zoom;
if (level > ImageHelper::zoomInFindLevel) {
return;
}
//内存中不存在的则主动获取
int count = listX.size();
for (int i = 0; i < count; ++i) {
int x = listX.at(i);
int y = listY.at(i);
QUrl url = listUrl.at(i);
QString flag = listFlag.at(i);
int scale = listScale.at(i);
QPixmap image = listImage.at(i);
//获取绘制用的瓦片图片并绘制/没有原图则取电子放大或者下级拼接的
this->getAndDrawPixmap(painter, url, flag, x, y, z, size, level, scale, image, offline);
}
//预加载瓦片区域往四周扩散部分/方便拖动快速绘制/不用再去临时加载
if (!offline && z <= m_tile_zoom) {
this->prefetchImage(z, left, top, right, bottom, offline);
}
//绘制网格文字
if (ImageHelper::drawTileInfo) {
this->drawInfo(painter, z, size, left, top, right, bottom);
}
}
QString LayerMap::getUrlFlag(QUrl &url, int x, int y, int z, int level) const
{
url = m_map_adapter->tileQuery(x >> level, y >> level, z - level);
return TileHelper::getUrlFlag(url.toString(), m_tile_size, m_epsg_type);
}
int LayerMap::findParentPixmap(QPixmap &pixmap, int x, int y, int z, int size, bool offline) const
{
//要过滤下最小级别不能小于1
int scale = 0;
int maxLevel = qMin(ImageHelper::zoomInFindLevel, z - 1);
for (int level = 1; level <= maxLevel; ++level) {
QUrl url;
QString flag = this->getUrlFlag(url, x, y, z, level);
pixmap = ImageHelper::findPixmap(flag);
//离线状态还需要从文件获取
if (offline && pixmap.isNull()) {
pixmap = m_map_image->getImage(url, flag, offline, false);
}
if (!pixmap.isNull()) {
scale = level;
MapHelper::copyPixmapGrid(pixmap, size, x, y, level);
break;
}
}
return scale;
}
void LayerMap::findChildPixmap(QPixmap &pixmap, int &scale, int x, int y, int z, int size, bool offline) const
{
//如果还没找到/往下级找四张拼起来/当前级一张瓦片对应下一级四张瓦片
if (!offline && ImageHelper::zoomOutFindLevel > 0 && pixmap.isNull()) {
int nextX = x * 2;
int nextY = y * 2;
int nextZ = z + 1;
QPixmap pixs[2][2];
for (int i = 0; i < 2; ++i) {
for (int j = 0; j < 2; ++j) {
QUrl url;
QString flag = this->getUrlFlag(url, nextX + i, nextY + j, nextZ, 0);
QPixmap pix = ImageHelper::findPixmap(flag);
if (!pix.isNull()) {
pixs[i][j] = pix;
} else {
//只要有一张没找到则不用继续
return;
}
}
}
//四张瓦片都找到则拼接成一张大图/正好是当前瓦片的完整区域
//pixs[i][j]中i是x偏移对应列/j是y偏移对应行/按坐标顺序绘制
scale = 255;
MapHelper::mergePixmapGrid(pixmap, size, pixs[0][0], pixs[1][0], pixs[0][1], pixs[1][1]);
}
}
void LayerMap::getAndDrawPixmap(QPainter *painter, QUrl url, QString flag, int x, int y, int z, int size, int level, int scale, const QPixmap &image, bool offline) const
{
//查找上一个层级的瓦片
if (level > 0) {
flag = this->getUrlFlag(url, x, y, z, level);
}
//获取到的上一级瓦片/还需要裁切对应区域
QPixmap pixmap = m_map_image->getImage(url, flag, offline, false);
if (level > 0 && !pixmap.isNull()) {
MapHelper::copyPixmapGrid(pixmap, size, x, y, level);
}
//找到了原图则用原图/没有则取电子放大或者下级拼接的
int level2 = level;
if (scale > 0 && pixmap.isNull()) {
level2 = scale;
pixmap = image;
}
//绘制瓦片到对应网格
this->drawPixmap(painter, pixmap, size, x, y, level2, offline, scale == 0);
}
void LayerMap::drawPixmap(QPainter *painter, QPixmap &pixmap, int size, int x, int y, int level, bool offline, bool fill) const
{
bool isNull = pixmap.isNull();
if (fill && isNull) {
pixmap = offline ? ImageHelper::getNotFoundPixmap(size) : ImageHelper::getLoadingPixmap(size);
}
//绘制瓦片到对应网格/可能是电子放大的图片/需要填充缩放
if (!pixmap.isNull()) {
MapHelper::scalePixmap(pixmap, size);
QPoint point(x * size, y * size);
painter->drawPixmap(point, pixmap);
//绘制查找到的瓦片层级/裁切的图片
if (!isNull && ImageHelper::drawTileInfo) {
QString text = QString::number(level > 0 ? level : 0);
OverlayBaseHelper::drawText(painter, point, text, Qt::green, size, Qt::AlignRight);
}
}
}
void LayerMap::drawGrid(QPainter *painter, int z, int size, int left, int top, int right, int bottom) const
{
static QPixmap pixmap = ImageHelper::getNotFoundPixmap(size);
for (int x = left; x <= right; ++x) {
for (int y = top; y <= bottom; ++y) {
if (m_map_adapter->isTileValid(x, y, z)) {
this->drawPixmap(painter, pixmap, size, x, y);
}
}
}
}
void LayerMap::drawInfo(QPainter *painter, const QPointF &point, int x, int y, int z, int size) const
{
QString text = QString("z=%3\nx=%1\ny=%2").arg(x).arg(y).arg(z);
OverlayBaseHelper::drawText(painter, point, text, Qt::red, size, Qt::AlignLeft | Qt::AlignVCenter);
}
void LayerMap::drawInfo(QPainter *painter, int z, int size, int left, int top, int right, int bottom) const
{
for (int x = left; x <= right; ++x) {
for (int y = top; y <= bottom; ++y) {
if (m_map_adapter->isTileValid(x, y, z)) {
QPoint point(x * size, y * size);
this->drawInfo(painter, point, x, y, z, size);
}
}
}
}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。