You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

325 lines
9.3 KiB

#include "GoogleTile.h"
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
#include <fstream>
#include <cmath>
#include <filesystem>
using std::string;
namespace fs = std::filesystem;
#define M_PI 3.1415926
googleTile::googleTile()
{
}
googleTile::~googleTile()
{
}
void googleTile::ExportGeoPng(cv::Mat _pan, TileInfo info, std::string dir)
{
// 创建目录
if (!fs::exists(dir))
{
fs::create_directories(dir);
}
// 保存全景图
string png_path = dir + "/" + info.tileName + ".png";
cv::imwrite(png_path, _pan);
// 写入kml
string kml_path = dir + "/" + info.tileName + ".kml";
vector<TileInfo> taskTiles;
taskTiles.push_back(info);
WriteKml(taskTiles, kml_path);
}
void googleTile::ExportTileSet(cv::Mat _pan, vector<TileInfo> taskTilesVec, std::string dir,std::string fileString)
{
int panWidth = _pan.cols;
int panHeight = _pan.rows;
int zoom = taskTilesVec[0].ind.z;
for (size_t i = 0; i < taskTilesVec.size(); i++)
{
TileInfo tile = taskTilesVec[i];
TileBox tilebox = tile.boxLatLon;
RECT32S tileBoxInPan = tile.boxInPan;
int x1 = tileBoxInPan.x;
int x2 = tileBoxInPan.x + tileBoxInPan.w;
int y1 = tileBoxInPan.y;
int y2 = tileBoxInPan.y + tileBoxInPan.h;
// 有不完整瓦片,不保存
if (x1 < 0 || y1 < 0 || x2 > panWidth - 1 || y2 > panHeight - 1)
{
continue;
}
// 确保坐标在图像范围内
x1 = std::max(0, std::min(x1, panWidth - 1));
y1 = std::max(0, std::min(y1, panHeight - 1));
x2 = std::max(0, std::min(x2, panWidth - 1));
y2 = std::max(0, std::min(y2, panHeight - 1));
// 提取瓦片
cv::Rect tileRect(x1, y1, x2 - x1, y2 - y1);
cv::Mat tileImg = _pan(tileRect);
// 标准web瓦片尺寸
if (tileImg.cols != 256 || tileImg.rows != 256)
{
cv::resize(tileImg, tileImg, cv::Size(256, 256));
}
// 生成文件名
std::string tileDir = dir + "/" + std::to_string(zoom);
std::string fileName = tileDir + "/" + std::to_string(tile.ind.x) + "_" + std::to_string(tile.ind.y) + ".png";
// 创建目录
if (!fs::exists(tileDir))
{
fs::create_directories(tileDir);
}
// 保存瓦片为图像文件
cv::imwrite(fileName, tileImg);
}
// 输出KML
WriteKml(taskTilesVec,dir + "/" + fileString + "_z" + std::to_string(zoom) + ".kml");
}
void googleTile::ExportOneTile(cv::Mat _pan, TileInfo tile, std::string dir, std::string fileString)
{
int panWidth = _pan.cols;
int panHeight = _pan.rows;
int zoom = tile.ind.z;
TileBox tilebox = tile.boxLatLon;
RECT32S tileBoxInPan = tile.boxInPan;
int x1 = tileBoxInPan.x;
int x2 = tileBoxInPan.x + tileBoxInPan.w;
int y1 = tileBoxInPan.y;
int y2 = tileBoxInPan.y + tileBoxInPan.h;
// 有不完整瓦片,不保存
if (x1 < 0 || y1 < 0 || x2 > panWidth - 1 || y2 > panHeight - 1)
{
return;
}
// 确保坐标在图像范围内
x1 = std::max(0, std::min(x1, panWidth - 1));
y1 = std::max(0, std::min(y1, panHeight - 1));
x2 = std::max(0, std::min(x2, panWidth - 1));
y2 = std::max(0, std::min(y2, panHeight - 1));
// 提取瓦片
cv::Rect tileRect(x1, y1, x2 - x1, y2 - y1);
cv::Mat tileImg = _pan(tileRect);
// 标准web瓦片尺寸
if (tileImg.cols != 256 || tileImg.rows != 256)
{
cv::resize(tileImg, tileImg, cv::Size(256, 256));
}
// 生成文件名
std::string tileDir = dir + "/" + std::to_string(zoom);
std::string fileName = tileDir + "/" + std::to_string(tile.ind.x) + "_" + std::to_string(tile.ind.y) + ".png";
std::string kmlPath = tileDir + "/" + std::to_string(tile.ind.x) + "_" + std::to_string(tile.ind.y) + ".kml";
tile.href = std::to_string(tile.ind.x) + "_" + std::to_string(tile.ind.y) + ".png";
// 创建目录
if (!fs::exists(tileDir))
{
fs::create_directories(tileDir);
}
// 保存瓦片为图像文件
cv::imwrite(fileName, tileImg);
vector<TileInfo> info;
info.push_back(tile);
WriteKml(info, kmlPath);
}
vector<TileInfo> googleTile::CalcTileOverlayVec(TileInfo areaInfo,int panWidth,int panHeight)
{
int zoom = areaInfo.ind.z;
TileBox panBox = areaInfo.boxLatLon;
// 计算四至的瓦片编号
TileIndex WN = LatLonToTile(panBox.north, panBox.west, zoom);
TileIndex WS = LatLonToTile(panBox.south, panBox.west, zoom);
TileIndex EN = LatLonToTile(panBox.north, panBox.east, zoom);
TileIndex ES = LatLonToTile(panBox.south, panBox.east, zoom);
int xStart = WN.x;
int xEnd = EN.x;
int yStart = EN.y;
int yEnd = ES.y;
vector<TileInfo> taskTiles;
for (size_t i = yStart; i < yEnd; i++)
{
for (size_t j = xStart; j < xEnd; j++)
{
TileIndex id = { j,i,zoom };
TileBox tilebox = GetTileBox(id);
// 瓦片不完整覆盖则不输出
if (tilebox.west < panBox.west|| panBox.north < tilebox.north
|| tilebox.east > panBox.east || panBox.south > tilebox.south)
{
continue;
}
// 计算瓦片在全景图中的像素位置
RECT32S rect = { 0 };
rect.x = (tilebox.west - panBox.west) / (panBox.east - panBox.west) * panWidth;
rect.y = (panBox.north - tilebox.north) / (panBox.north - panBox.south) * panHeight;
rect.w = (tilebox.east - panBox.west) / (panBox.east - panBox.west) * panWidth - rect.x;
rect.h = (panBox.north - tilebox.south) / (panBox.north - panBox.south) * panHeight - rect.y;
// 填充
TileInfo info = { 0 };
info.boxLatLon = tilebox;
info.ind = id;
info.boxInPan = rect;
// 补充瓦片信息
info.tileName = std::to_string(j) + "_" + std::to_string(i) + "_" + std::to_string(zoom);
info.href = std::to_string(zoom) + "/" + std::to_string(j) + "_" + std::to_string(i) + ".png";
taskTiles.push_back(info);
}
}
return taskTiles;
}
int googleTile::ZoomLevel(float mp)
{
int nLev = 0;
// 计算单位瓦片的实际覆盖
5 months ago
int meters_cover = TILE_SIZE * 1.0/mp;
int earthLen = 40075017;
nLev = std::floor(std::log2(earthLen / meters_cover)) + 1;
return nLev;
}
TileIndex googleTile::LatLonToTile(double latitude, double longitude, int zoomLevel)
{
// 将纬度转换为墨卡托投影
double latRad = latitude * M_PI / 180.0;
double sinLat = sin(latRad);
// 计算经度的瓦片编号0-2^zoomLevel
int xTile = static_cast<int>((longitude + 180.0) / 360.0 * pow(2.0, zoomLevel));
// 计算纬度的瓦片编号0-2^zoomLevel
int yTile = static_cast<int>((1.0 - log((1.0 + sinLat) / (1.0 - sinLat)) / (2.0 * M_PI)) / 2.0 * pow(2.0, zoomLevel));
return TileIndex{ xTile, yTile, zoomLevel };
}
// 将瓦片编号转换为经纬度
void TileToLatLon(int x, int y, int z, double& lat, double& lon)
{
double n = std::pow(2.0, z);
lon = x / n * 360.0 - 180.0;
double lat_rad = std::atan(std::sinh(M_PI * (1 - 2 * y / n)));
lat = lat_rad * 180.0 / M_PI;
}
TileBox googleTile::GetTileBox(TileIndex ind)
{
TileBox box = {0};
// 计算瓦片的西北角经纬度
TileToLatLon(ind.x, ind.y, ind.z, box.north, box.west);
// 计算瓦片的东南角经纬度
TileToLatLon(ind.x + 1, ind.y + 1, ind.z, box.south, box.east);
return box;
}
void googleTile::WriteKml(vector<TileInfo> tiles, string filePath)
{
// Open the KML file for writing
std::ofstream kml_file(filePath);
if (!kml_file.is_open()) {
std::cerr << "Failed to open KML file for writing: " << filePath << std::endl;
return;
}
// Write the KML header
kml_file << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl;
kml_file << "<kml xmlns=\"http://www.opengis.net/kml/2.2\">" << std::endl;
kml_file << "<Document>" << std::endl;
// Loop through each tile and write a GroundOverlay element
for (const auto& tile : tiles)
{
kml_file << "<GroundOverlay>" << std::endl;
kml_file << " <name>" << tile.tileName << "</name>" << std::endl;
kml_file << " <Icon>" << std::endl;
kml_file << " <href>" << tile.href << "</href>" << std::endl;
kml_file << " </Icon>" << std::endl;
kml_file << " <LatLonBox>" << std::endl;
kml_file << " <north>" << std::fixed << std::setprecision(6) << tile.boxLatLon.north << "</north>" << std::endl;
kml_file << " <south>" << std::fixed << std::setprecision(6) << tile.boxLatLon.south << "</south>" << std::endl;
kml_file << " <east>" << std::fixed << std::setprecision(6) << tile.boxLatLon.east << "</east>" << std::endl;
kml_file << " <west>" << std::fixed << std::setprecision(6) << tile.boxLatLon.west << "</west>" << std::endl;
kml_file << " </LatLonBox>" << std::endl;
kml_file << "</GroundOverlay>" << std::endl;
}
// Write the KML footer
kml_file << "</Document>" << std::endl;
kml_file << "</kml>" << std::endl;
// Close the file
kml_file.close();
}