RasterProcessTool/RasterMainWidgetGUI/RasterMainWidget/tmsprovider.cpp

217 lines
5.8 KiB
C++
Raw Normal View History

2025-02-07 09:16:22 +00:00
#include <QImage>
#include <QPainter>
#include <QThreadPool>
#include <QDir>
#include <QSqlQuery>
#include <QSqlError>
#include <QPointer>
#include <tmsprovider.h>
#include <network.h>
#include <iostream>
2025-02-07 09:16:22 +00:00
#pragma execution_character_set("utf-8")
namespace LAMPMainWidget
{
TileDownloadTask::TileDownloadTask(TileInfo tile, tileReadyCallback cb, QObject* parent)
2025-04-01 10:23:24 +00:00
: QObject(parent),
mTile(std::move(tile)),
mCallback(cb)
2025-04-01 10:23:24 +00:00
{
// 程序初始化时注册如main函数或类构造函数
qRegisterMetaType<TileInfo>("TileInfo");
2025-04-01 10:23:24 +00:00
}
void TileDownloadTask::run()
2025-04-01 10:23:24 +00:00
{
Network web{};
QByteArray data = web.httpsRequest(mTile.url);
mTile.data = data;
if (mCallback) {
mCallback(mTile);
}
else {}
2025-04-01 10:23:24 +00:00
}
TmsProvider::TmsProvider(QObject* parent)
: LayerProvider(parent),
mImage(nullptr)
{
if (!QFile::exists(mDbName)) {
QFile dbFile{ mDbName };
dbFile.open(QIODevice::ReadWrite);
dbFile.close();
}
}
TmsProvider::~TmsProvider()
{
delete mImage;
}
bool TmsProvider::initCache()
2025-04-01 10:23:24 +00:00
{
mDbConn = QSqlDatabase::addDatabase("QSQLITE", id());
mDbConn.setDatabaseName(mDbName);
if (!mDbConn.open()) {
std::cout << u8"缓存数据库打开失败";
2025-04-01 10:23:24 +00:00
return false;
}
QSqlQuery sql{ mDbConn };
if (!mDbConn.tables().contains(mTableName)) {
sql.prepare(QString("create table %1 ("
"id integer primary key autoincrement,"
"provider text not null,"
"zoom integer not null,"
"position text not null,"
"data blob not null"
")").arg(mTableName));
if (!sql.exec()) {
qCritical() << "缓存表创建失败" << sql.lastError().text();
return false;
}
}
mDbConn.close();
return true;
}
QByteArray TmsProvider::getCache(const QPoint& pos, int zoom)
2025-04-01 10:23:24 +00:00
{
QByteArray res{};
mDbConn.open();
QSqlQuery select{ mDbConn };
select.prepare(QString("select data from %1 "
"where provider = :provider "
"and zoom = :zoom "
"and position = :position "
"order by id desc limit 1"
).arg(mTableName));
select.bindValue(":provider", id());
select.bindValue(":zoom", zoom);
select.bindValue(":position", QString("%1:%2").arg(pos.x()).arg(pos.y()));
if (!select.exec()) {
std::cout << pos.x()<<","<<pos.y() << "查询失败=>" << select.lastError().text().toUtf8().constData() << std::endl;
2025-04-01 10:23:24 +00:00
mDbConn.close();
return res;
}
if (!select.seek(0)) {
mDbConn.close();
return res;
}
res = select.value(0).toByteArray();
mDbConn.close();
return res;
}
bool TmsProvider::addCache(const QPoint& pos, int zoom, QByteArray data)
2025-04-01 10:23:24 +00:00
{
if (data.isEmpty()) {
std::cout << "瓦片数据为空";
2025-04-01 10:23:24 +00:00
return false;
}
mDbConn.open();
QSqlQuery sql{ mDbConn };
sql.prepare(QString{
"insert into %1 (provider, zoom, position, data)"
"values( :provider, :zoom, :position, :data)"
}.arg(mTableName));
sql.bindValue(":tbname", mTableName);
sql.bindValue(":provider", id());
sql.bindValue(":zoom", zoom);
sql.bindValue(":position", QString("%1:%2").arg(pos.x()).arg(pos.y()));
sql.bindValue(":data", data);
if (!sql.exec()) {
std::cout << pos.x() << "," << pos.y() << "=>" << sql.lastError().text().toUtf8().constData() << std::endl;
2025-04-01 10:23:24 +00:00
mDbConn.close();
return false;
}
mDbConn.close();
return true;
}
bool TmsProvider::cacheContains(const QPoint& pos, int zoom)
2025-04-01 10:23:24 +00:00
{
QByteArray res = getCache(pos, zoom);
return !res.isEmpty();
}
void TmsProvider::createTask(const QRectF& rect, int zoom)
2025-04-01 10:23:24 +00:00
{
newImage(rect);
/// 创建下载瓦片任务
const QSize sz = tileSize();
int xMin = qFloor(rect.topLeft().x() / sz.width());
double xOffset = rect.topLeft().x() / sz.width() - xMin;
int xMax = qFloor(rect.bottomRight().x() / sz.width());
int yMin = qFloor(rect.topLeft().y() / sz.height());
double yOffset = rect.topLeft().y() / sz.height() - yMin;
int yMax = qFloor(rect.bottomRight().y() / sz.height());
for (int i = xMin; i <= xMax; ++i) {
for (int j = yMin; j <= yMax; ++j) {
TileInfo tile{};
tile.index = QPointF{ i - xMin - xOffset, j - yMin - yOffset };
tile.position = QPoint{ i, j };
tile.zoom = zoom;
tile.coord = QPoint{ i - xMin, j - yMin };
tile.url = tileUrl(PointXY{ static_cast<double>(i), static_cast<double>(j) }, zoom);
auto tileData = getCache(tile.position, tile.zoom);
if (!tileData.isEmpty()) {
tile.data = tileData;
tileReady(tile);
continue;
}
/* 原始写法遇到了跨线程信号槽失效问题,这里修改为回调函数方式
2025-04-01 10:23:24 +00:00
auto* task = new TileDownloadTask(tile);
if (!QObject::connect(task, SIGNAL(tileReady(TileInfo)), this, SLOT(tileReady(TileInfo)))) {
qDebug() << "连接失败:" << QObject::tr(qPrintable(Q_FUNC_INFO));
}
QThreadPool::globalInstance()->start(task);
*/
// 回调函数版本
QPointer<TmsProvider> safeThis(this);
auto* task = new TileDownloadTask(tile, [safeThis]( TileInfo info) {
if (safeThis) { // 自动检测对象是否存活
QMetaObject::invokeMethod(safeThis.data(), [safeThis, info] {
safeThis->tileReady(info);
}, Qt::QueuedConnection); // 强制主线程执行[6](@ref)
}
});
2025-04-01 10:23:24 +00:00
QThreadPool::globalInstance()->start(task);
2025-04-01 10:23:24 +00:00
}
}
}
void TmsProvider::tileReady(TileInfo tile)
2025-04-01 10:23:24 +00:00
{
if (!cacheContains(tile.position, tile.zoom)) {
addCache(tile.position, tile.zoom, tile.data);
}
QPainter painter{ mImage };
QImage img = QImage::fromData(tile.data);
if (img.isNull()) {
return;
}
double xPos = tile.index.x() * tileSize().width();
double yPos = tile.index.y() * tileSize().height();
painter.drawImage(QPointF(xPos, yPos), img);
}
void TmsProvider::newImage(const QRectF& rect)
2025-04-01 10:23:24 +00:00
{
QSize imgSize{ int(rect.width()), int(rect.height()) };
if (!mImage || imgSize != mImage->size()) {
mImage = new QImage(imgSize, QImage::Format_RGB32);
}
mImage->fill(Qt::white);
}
2025-02-07 09:16:22 +00:00
}