1、功能概述
卫星地图影像Tile下载器,详细功能包括如下几点:
- 1、简洁界面,操作明了;MFC界面,可以快速修改为Qt界面;
- 2、可以依据配置下载各类卫星影像、街道路网、地形、海洋、高程的瓦片Tile数据;
- 3、默认下载ArcGis,可通过配置支持天地图、谷歌、高德、百度等地图源;
- 4、支持多线程下载,进度条显示实时进度和下载时间;
- 5、支持将各级别Tile瓦片进行拼接形成大图,1000张小图拼接为1张大图,速度大约2秒;
- 6、支持预览各级别影像图片;
- 7、简单美化界面后可以作为课程设计或大作业进行提交。
2、功能介绍
2.1 地图资源
不同的地图源都有其对应的瓦片Tile下载地址,下面以ArcGIS举例说明:
//卫星影像
https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/0/0/0.jpg;
//街道路网
https://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/;
//地形
https://server.arcgisonline.com/ArcGIS/rest/services/World_Physical_Map/MapServer/tile/;
//海洋
ttps://server.arcgisonline.com/ArcGIS/rest/services/Ocean/World_Ocean_Base/MapServer/tile/;
//山影
https://services.arcgisonline.com/arcgis/rest/services/Elevation/World_Hillshade/MapServer/tile/;
2.2 瓦片下载
地图源对应的瓦片Tile下载地址可以直接在浏览器打开为图片,因此我们仅需要利用工具把它保存下来即可,此处采用的是libcurl库,由于下载链接为https,因此libcurl库的编译版本需要支持openssl加密,源码中已经附带了编译好的libcurl库,可以直接使用。如果采用不支持openssl的库,会出现图片无法下载的错误。下载功能比较简单:
size_t write_data(char* buffer, size_t size, size_t nitems, void* outstream)
{
size_t written = fwrite(buffer, size, nitems, (FILE*)outstream);
return written;
}
CURL* pCurl;
pCurl = curl_easy_init();
FILE* pFile = nullptr;
fopen_s(&pFile, sFilePath.c_str(), "wb");
curl_easy_setopt(pCurl, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_easy_setopt(pCurl, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_easy_setopt(pCurl, CURLOPT_WRITEDATA, (void*)pFile);
curl_easy_setopt(pCurl, CURLOPT_WRITEFUNCTION, write_data);
curl_easy_setopt(pCurl, CURLOPT_URL, sUrl.c_str());
CURLcode resCode = curl_easy_perform(pCurl);
fclose(pFile);
2.3 自定义进度条
MFC提供的进度条比较不美观。如果用Qt的话不用自己重绘进度条。既然用MFC了,就顺手美化一下进度条吧。
void CMyProgressCtrl::OnPaint()
{
CPaintDC dc(this); // device context for painting
// TODO: Add your message handler code here
CRect cRect;
GetClientRect(cRect);
CString sPos;
int nLower,nUpper;
CRect rcRange = cRect;
GetRange(nLower,nUpper);
if(nUpper - nLower > 0)
{
sPos.Format(_T("%d%%"),100*GetPos()/(nUpper-nLower) );
rcRange.right = (int)(rcRange.left + rcRange.Width() * (double)GetPos()/(nUpper-nLower));
}
else
{
sPos = "0%";
dc.FillSolidRect(cRect, GetSysColor(COLOR_3DFACE));
dc.Draw3dRect(cRect, RGB(0,0,0), RGB(0,0,0));
dc.SetBkMode(TRANSPARENT);
dc.DrawText(sPos, cRect, DT_CENTER);
return;
}
CDC memDC,memDC2;
CBitmap bmp,bmp2;
bmp.CreateCompatibleBitmap(&dc,cRect.Width(),cRect.Height());
memDC.CreateCompatibleDC(&dc);
memDC.SelectObject(&bmp);
bmp2.CreateCompatibleBitmap(&dc,cRect.Width(),cRect.Height());
memDC2.CreateCompatibleDC(&dc);
memDC2.SelectObject(&bmp2);
memDC2.FillSolidRect(cRect, RGB(255,0,0)); //底色用RGB(255,0,0)
memDC2.FillSolidRect(rcRange, RGB(0,255,255)); //已完成的进度用RGB(0,255,255)填充
memDC.FillSolidRect(cRect, RGB(0,0,0));
memDC.SetBkMode(TRANSPARENT);
memDC.SetTextColor(RGB(255,0,0));
memDC.DrawText(sPos, cRect, DT_CENTER|DT_VCENTER|DT_SINGLELINE); //memDC中现在是黑底红字
//SRCPAINT是OR操作, 所以现在memDC2中, 落在已完成进度区域内的字是白色(0|255, 255|255, 255|255), 落在未完成区域内的字和底色融为一体了(255|255, 0|0, 0|0)。
memDC2.BitBlt(0, 0, cRect.Width(), cRect.Height(), &memDC, 0, 0, SRCPAINT);
memDC.FillSolidRect(cRect, RGB(255,0,0));
//把已完成部分的底色RGB(0,255,255)设为透明色,现在memDC中只剩下红底白字了。
//memDC.TransparentBlt(0,0,cRect.Width(),cRect.Height(),&memDC2,0,0,cRect.Width(),cRect.Height(),RGB(0,255,255));
TransparentBlt(memDC.m_hDC,0,0,cRect.Width(),cRect.Height(),memDC2.m_hDC,0,0,cRect.Width(),cRect.Height(),RGB(0,255,255));
memDC2.FillSolidRect(cRect, GetSysColor(COLOR_3DFACE));
memDC2.SetBkMode(TRANSPARENT);
memDC2.SetTextColor(RGB(0,0,0));
memDC2.DrawText(sPos, cRect, DT_CENTER|DT_VCENTER|DT_SINGLELINE); //用黑色绘制文字
//绘制已完成进度的进度条,完成后覆盖了已完成区域内的黑色文字
for(int i=rcRange.top; i<rcRange.bottom; i++)
{
CPen pen, *pOldPen;
pen.CreatePen(PS_SOLID, 1, GetColor(i-rcRange.top, rcRange.Height()));
pOldPen = memDC2.SelectObject(&pen);
memDC2.MoveTo(rcRange.left, i);
memDC2.LineTo(rcRange.right, i);
memDC2.SelectObject(pOldPen);
}
//把memDC中的白字部分叠加到memDC2中,完成进度条绘制。
//memDC2.TransparentBlt(0,0,cRect.Width(),cRect.Height(),&memDC,0,0,cRect.Width(),cRect.Height(),RGB(255,0,0));
TransparentBlt(memDC2.m_hDC,0,0,cRect.Width(),cRect.Height(),memDC.m_hDC,0,0,cRect.Width(),cRect.Height(),RGB(255,0,0));
memDC2.Draw3dRect(0, 0, cRect.Width(), cRect.Height(), RGB(0,0,0), RGB(0,0,0));
dc.BitBlt(0,0,cRect.Width(),cRect.Height(),&memDC2,0,0,SRCCOPY);
memDC2.DeleteDC();
memDC.DeleteDC();
bmp.DeleteObject();
bmp2.DeleteObject();
// Do not call CProgressCtrl::OnPaint() for painting messages
}
2.4 加载影像
MFC提供的pictureCtrl控件可以直接加载图片,不过要用到CImage类。CImage类的具体用法可以参考另一篇博客:图像处理必须要用OpenCV 吗? ??试一下CImage类像素级处理图像:复制、截屏、拼接、裁剪、缩放、灰度图!
CDC* pdc = GetDlgItem(IDC_STATIC_MAP)->GetWindowDC();
CRect rect;
GetDlgItem(IDC_STATIC_MAP)->GetClientRect(&rect);
CImage image;
image.Load(strCacheFile);
image.Draw(pdc->m_hDC, rect);
CString strShowLevel;
strShowLevel.Format(_T("显示等级:%d"), 0);
GetDlgItem(IDC_STATIC_SHOWLEVEL)->SetWindowText(strShowLevel);
2.5 拼接瓦片
瓦片数据被分为一片片,预览时候需要拼成一张大图来进行加载,因此需要实现图片拼接功能。利用CImage类可以将图片进行拼接。具体拼接顺序和方式可以依据需要自己进行调整。具体用法可以参考另一篇博客:图像处理必须要用OpenCV 吗? ??试一下CImage类像素级处理图像:复制、截屏、拼接、裁剪、缩放、灰度图!
int nHeight = nCount * 256;
int nWidth = nCount * 256;
CImage imageDest;
imageDest.Create(nWidth, nHeight, 24);
HDC hDCImgDest = imageDest.GetDC();
for (int i = 0; i < vecTiles.size(); i++)
{
CString strImagePath = vecTiles.at(i).strFilePath;
int nX = vecTiles.at(i).nX;
int nY = vecTiles.at(i).nY;
CImage imageOrign;
HRESULT hResult = imageOrign.Load(strImagePath);
BitBlt(hDCImgDest, nX, nY, 256, 256, imageOrign.GetDC(), 0, 0, SRCCOPY);
imageOrign.ReleaseDC();
}
imageDest.ReleaseDC();
HRESULT hRes = S_FALSE;
hRes = imageDest.Save(strOutputPath);
if (!SUCCEEDED(hRes))
{
MessageBox(_T("合并图像文件失败!"));
}