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

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 "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;
// 计算单位瓦片的实际覆盖
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();
}