更多精彩内容 |
---|
个人内容分类汇总 |
GIS开发 |
- 支持多线程加载显示本地离线瓦片地图(墨卡托投影);
- 瓦片切片规则以左上角为原点(谷歌、高德、ArcGis等),不支持百度瓦片规则;
- 支持显示瓦片网格、编号信息。
- 支持鼠标滚轮缩放切换地图层级。
- 支持鼠标拖拽。
- 采用z/x/y层级瓦片存储格式。
- 在单文件中实现所有主要功能,简单便于理解。
- 以北纬85.05,西经-180为坐标原点【绝对像素坐标】。
开发环境说明
使用瓦片地图工具下载z/x/y存储格式的瓦片地图进行显示。
bingformula.h
#ifndef BINGFORMULA_H
#define BINGFORMULA_H
#include
#include
namespace Bing {
qreal clip(qreal n, qreal min, qreal max);
qreal clipLon(qreal lon); // 裁剪经度范围
qreal clipLat(qreal lat); // 裁剪纬度范围
uint mapSize(int level); // 根据地图级别计算世界地图总宽高(以像素为单位)
qreal groundResolution(qreal lat, int level); // 计算地面分辨率
qreal mapScale(qreal lat, int level, int screenDpi); // 计算比例尺
QPoint latLongToPixelXY(qreal lon, qreal lat, int level); // 经纬度转像素 XY坐标
void pixelXYToLatLong(QPoint pos, int level, qreal& lon, qreal& lat); // 像素坐标转WGS-84墨卡托坐标
QPoint pixelXYToTileXY(QPoint pos); // 像素坐标转瓦片编号
QPoint tileXYToPixelXY(QPoint tile); // 瓦片编号转像素坐标
QPoint latLongToTileXY(qreal lon, qreal lat, int level); // 经纬度转瓦片编号
QPointF tileXYToLatLong(QPoint tile, int level); // 瓦片编号转经纬度
QString tileXYToQuadKey(QPoint tile, int level); // 瓦片编号转QuadKey
void quadKeyToTileXY(QString quadKey, int& tileX, int& tileY, int& level); // QuadKey转瓦片编号、级别
} // namespace Bing
#endif // BINGFORMULA_H
bingformula.cpp
/********************************************************************
* 文件名: bingformula.cpp
* 时间: 2024-04-05 21:36:16
* 开发者: mhf
* 邮箱: [email protected]
* 说明: 适用于Bing瓦片地图的算法
* ******************************************************************/
#include "bingformula.h"
#include
#include
static const qreal g_EarthRadius = 6'378'137; // 赤道半径
/**
* @brief 限定最小值,最大值范围
* @param n 需要限定的值
* @param min
* @param max
* @return
*/
qreal Bing::clip(qreal n, qreal min, qreal max)
{
n = qMax(n, min);
n = qMin(n, max);
return n;
}
/**
* @brief 限定经度范围值,防止超限,经度范围[-180, 180]
* @param lon 输入的经度
* @return 裁剪后的经度
*/
qreal Bing::clipLon(qreal lon)
{
return clip(lon, -180.0, 180);
}
/**
* @brief 限定纬度范围值,防止超限,经度范围[-85.05112878, 85.05112878]
* @param lat 输入的纬度
* @return 裁剪后的纬度
*/
qreal Bing::clipLat(qreal lat)
{
return clip(lat, -85.05112878, 85.05112878);
}
/**
* @brief 根据输入的瓦片级别计算全地图总宽高,适用于墨卡托投影
* @param level 1-23(bing地图没有0级别,最低级别为1,由4块瓦片组成)
* @return 以像素为单位的地图宽度和高度。
*/
uint Bing::mapSize(int level)
{
uint w = 256; // 第0级别为256*256
return (w << level);
}
/**
* @brief 计算指定纬度、级别的地面分辨率(不同纬度分辨率不同)
* @param lat 纬度
* @param level 地图级别 1-23(bing地图没有0级别,最低级别为1,由4块瓦片组成)
* @return 地面分辨率 单位(米/像素)
*/
qreal Bing::groundResolution(qreal lat, int level)
{
lat = clipLat(lat);
return qCos(lat * M_PI / 180) * 2 * M_PI * g_EarthRadius / mapSize(level);
}
/**
* @brief 计算地图比例尺,地面分辨率和地图比例尺也随纬度而变化
* @param lat 纬度
* @param level 地图级别 1-23(bing地图没有0级别,最低级别为1,由4块瓦片组成)
* @param screenDpi 屏幕分辨率,单位为点/英寸 通常为 96 dpi
* @return 地图比例尺 1:N(地图上1厘米表示实际N厘米)
*/
qreal Bing::mapScale(qreal lat, int level, int screenDpi)
{
return groundResolution(lat, level) * screenDpi / 0.0254; // 1英寸等于0.0254米
}
/**
* @brief 将一个点从纬度/经度WGS-84墨卡托坐标(以度为单位)转换为指定细节级别的像素XY坐标。
* @param lon 经度
* @param lat 纬度
* @param level 地图级别
* @return 像素坐标
*/
QPoint Bing::latLongToPixelXY(qreal lon, qreal lat, int level)
{
lon = clipLon(lon);
lat = clipLat(lat);
qreal x = (lon + 180) / 360;
qreal sinLat = qSin(lat * M_PI / 180);
qreal y = 0.5 - qLn((1 + sinLat) / (1 - sinLat)) / (4 * M_PI);
uint size = mapSize(level);
qreal pixelX = x * size + 0.5;
pixelX = clip(pixelX, 0, size - 1);
qreal pixelY = y * size + 0.5;
pixelY = clip(pixelY, 0, size - 1);
return QPoint(pixelX, pixelY);
}
/**
* @brief 将像素从指定细节级别的像素XY坐标转换为经纬度WGS-84坐标(以度为单位)
* @param pos 像素坐标
* @param level
* @param lon
* @param lat
*/
void Bing::pixelXYToLatLong(QPoint pos, int level, qreal& lon, qreal& lat)
{
uint size = mapSize(level);
qreal x = (clip(pos.x(), 0, size - 1) / size) - 0.5;
qreal y = 0.5 - (clip(pos.y(), 0, size - 1) / size);
lon = x * 360;
lat = 90 - (360 * qAtan(qExp(-y * 2 * M_PI)) / M_PI);
}
/**
* @brief 像素坐标转瓦片编号
* @param pos 像素坐标
* @return 瓦片编号
*/
QPoint Bing::pixelXYToTileXY(QPoint pos)
{
int x = pos.x() / 256;
int y = pos.y() / 256;
return QPoint(x, y);
}
/**
* @brief 瓦片编号转像素坐标
* @param tile 瓦片编号
* @return 像素坐标
*/
QPoint Bing::tileXYToPixelXY(QPoint tile)
{
int x = tile.x() * 256;
int y = tile.y() * 256;
return QPoint(x, y);
}
/**
* @brief 经纬度转瓦片编号
* @param lon
* @param lat
* @param level
* @return
*/
QPoint Bing::latLongToTileXY(qreal lon, qreal lat, int level)
{
return pixelXYToTileXY(latLongToPixelXY(lon, lat, level));
}
/**
* @brief 瓦片编号转经纬度
* @param tile
* @param level
* @return 经纬度 x:经度 y纬度
*/
QPointF Bing::tileXYToLatLong(QPoint tile, int level)
{
qreal lon = 0;
qreal lat = 0;
QPoint pos = tileXYToPixelXY(tile);
pixelXYToLatLong(pos, level, lon, lat);
return QPointF(lon, lat);
}
/**
* @brief 瓦片编号转 bing请求的QuadKey
* @param tile 瓦片编号
* @param level 瓦片级别
* @return
*/
QString Bing::tileXYToQuadKey(QPoint tile, int level)
{
QString key;
for (int i = level; i > 0; i--)
{
char digit = '0';
int mask = 1 << (i - 1);
if ((tile.x() & mask) != 0)
{
digit++;
}
if ((tile.y() & mask) != 0)
{
digit += 2;
}
key.append(digit);
}
return key;
}
/**
* @brief 将一个QuadKey转换为瓦片XY坐标。
* @param quadKey
* @param tileX 返回瓦片X编号
* @param tileY 返回瓦片Y编号
* @param level 返回瓦片等级
*/
void Bing::quadKeyToTileXY(QString quadKey, int& tileX, int& tileY, int& level)
{
tileX = 0;
tileY = 0;
level = quadKey.count();
QByteArray buf = quadKey.toUtf8();
for (int i = level; i > 0; i--)
{
int mask = 1 << (i - 1);
switch (buf.at(i - 1))
{
case '0':
break;
case '1':
tileX |= mask;
break;
case '2':
tileY |= mask;
break;
case '3':
tileX |= mask;
tileY |= mask;
break;
default:
break;
}
}
}
mapgraphicsview.h文件
#ifndef MAPGRAPHICSVIEW_H
#define MAPGRAPHICSVIEW_H
#include "mapStruct.h"
#include
class MapGraphicsView : public QGraphicsView
{
Q_OBJECT
public:
explicit MapGraphicsView(QWidget* parent = nullptr);
~MapGraphicsView() override;
void setRect(QRect rect);
void drawImg(const ImageInfo& info);
void clear();
signals:
void updateImage(const ImageInfo& info); // 添加瓦片图
void zoom(bool flag); // 缩放 true:放大
void showRect(QRect rect);
void mousePos(QPoint pos);
protected:
void mouseMoveEvent(QMouseEvent* event) override;
void wheelEvent(QWheelEvent* event) override;
private:
void getShowRect(); // 获取显示范围
private:
QGraphicsScene* m_scene = nullptr;
QPointF m_pos;
QPointF m_scenePos;
};
#endif // MAPGRAPHICSVIEW_H
mapgraphicsview.cpp文件
#include "mapgraphicsview.h"
#include "bingformula.h"
#include
#include
#include
#include
#include
MapGraphicsView::MapGraphicsView(QWidget* parent)
: QGraphicsView(parent)
{
m_scene = new QGraphicsScene();
this->setScene(m_scene);
this->setDragMode(QGraphicsView::ScrollHandDrag); // 鼠标拖拽
this->setMouseTracking(true); // 开启鼠标追踪
connect(this, &MapGraphicsView::updateImage, this, &MapGraphicsView::drawImg);
}
MapGraphicsView::~MapGraphicsView() {}
/**
* @brief 缩放后设置场景大小范围
* @param rect
*/
void MapGraphicsView::setRect(QRect rect)
{
m_scene->setSceneRect(rect);
// 将显示位置移动到缩放之前的位置
this->horizontalScrollBar()->setValue(qRound(m_scenePos.x() - m_pos.x()));
this->verticalScrollBar()->setValue(qRound(m_scenePos.y() - m_pos.y()));
getShowRect();
}
/**
* @brief 绘制瓦片图
* @param info
*/
void MapGraphicsView::drawImg(const ImageInfo& info)
{
// 绘制瓦片图
auto item = m_scene->addPixmap(info.img);
QPoint pos = Bing::tileXYToPixelXY(QPoint(info.x, info.y));
item->setPos(pos);
// 绘制边框
auto itemR = m_scene->addRect(0, 0, 255, 255, QPen(Qt::red));
itemR->setPos(pos);
}
/**
* @brief 清空所有瓦片
*/
void MapGraphicsView::clear()
{
m_scene->clear();
}
/**
* @brief 获取鼠标移动坐标
* @param event
*/
void MapGraphicsView::mouseMoveEvent(QMouseEvent* event)
{
QGraphicsView::mouseMoveEvent(event);
emit mousePos(this->mapToScene(event->pos()).toPoint());
getShowRect();
}
/**
* @brief 鼠标滚轮缩放
* @param event
*/
void MapGraphicsView::wheelEvent(QWheelEvent* event)
{
m_pos = event->pos(); // 鼠标相对于窗口左上角的坐标
m_scenePos = this->mapToScene(event->pos()); // 鼠标在场景中的坐标
if (event->angleDelta().y() > 0)
{
m_scenePos = m_scenePos * 2; // 放大
emit this->zoom(true);
}
else
{
m_scenePos = m_scenePos / 2; // 缩小
emit this->zoom(false);
}
}
/**
* @brief 获取当前场景的显示范围(场景坐标系)
*/
void MapGraphicsView::getShowRect()
{
QRect rect;
rect.setTopLeft(this->mapToScene(0, 0).toPoint());
rect.setBottomRight(this->mapToScene(this->width(), this->height()).toPoint());
emit this->showRect(rect);
}