RasterProcessTool/RasterMainWidgetGUI/RasterMainWidget/tmsprovider.cpp

217 lines
5.8 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include <QImage>
#include <QPainter>
#include <QThreadPool>
#include <QDir>
#include <QSqlQuery>
#include <QSqlError>
#include <QPointer>
#include <tmsprovider.h>
#include <network.h>
#include <iostream>
#pragma execution_character_set("utf-8")
namespace LAMPMainWidget
{
TileDownloadTask::TileDownloadTask(TileInfo tile, tileReadyCallback cb, QObject* parent)
: QObject(parent),
mTile(std::move(tile)),
mCallback(cb)
{
// 程序初始化时注册如main函数或类构造函数
qRegisterMetaType<TileInfo>("TileInfo");
}
void TileDownloadTask::run()
{
Network web{};
QByteArray data = web.httpsRequest(mTile.url);
mTile.data = data;
if (mCallback) {
mCallback(mTile);
}
else {}
}
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()
{
mDbConn = QSqlDatabase::addDatabase("QSQLITE", id());
mDbConn.setDatabaseName(mDbName);
if (!mDbConn.open()) {
std::cout << u8"缓存数据库打开失败";
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)
{
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;
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)
{
if (data.isEmpty()) {
std::cout << "瓦片数据为空";
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;
mDbConn.close();
return false;
}
mDbConn.close();
return true;
}
bool TmsProvider::cacheContains(const QPoint& pos, int zoom)
{
QByteArray res = getCache(pos, zoom);
return !res.isEmpty();
}
void TmsProvider::createTask(const QRectF& rect, int zoom)
{
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;
}
/* 原始写法遇到了跨线程信号槽失效问题,这里修改为回调函数方式
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)
}
});
QThreadPool::globalInstance()->start(task);
}
}
}
void TmsProvider::tileReady(TileInfo tile)
{
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)
{
QSize imgSize{ int(rect.width()), int(rect.height()) };
if (!mImage || imgSize != mImage->size()) {
mImage = new QImage(imgSize, QImage::Format_RGB32);
}
mImage->fill(Qt::white);
}
}