Qt加载离线地图
一、例子
QWebEngineView加載谷歌離線地圖
QT_地圖導航 源碼下載
Graphics View實現簡單離線地圖
Qt QGraphics類應用——地圖縮放選點
Qt QGraphics類應用——圖片移動+選點縮放+控制移動區域
QGraphicsView 如何實現百度地圖按照鼠標點進行放大縮小效果
二、項目
1、/QtMapViewer_With_QGraphicsView-master/MapViewer
在線下載瓦片地圖,QGraphicsView顯示,可以成功運行的。
2、/MapGraphics-master/MapGraphics
在線加載瓦片地圖,有方格線
瓦片數據緩存在:C:\Users\xxx\.MapGraphicsCache\OpenStreetMap Tiles三、基礎知識
1、TMS
TMS(Tile Map Service)、WMTS(Web Map Tile Service)瓦片服務
TMS瓦片
瓦片XY的計算
TMS下載文件的排列(TMS和google的區別)
wiki關于瓦片計算的代碼
切圖工具 MapTiler
2、切片坐標的計算:
(0)墨卡托投影
- X軸:赤道半徑為6378137米,則赤道周長為2πr = 20037508.3427892,因此X軸的取值范圍:[-20037508.3427892, 20037508.3427892]。
- y軸:本來緯度接近90度的時候,y軸是接近無限,但是為了在web上好計算,y軸的取值范圍也定為了[-20037508.3427892, 20037508.3427892] 。
- 由上面的x軸和y軸的取值范圍,反計算得到經緯度的取值范圍為:
經度: [-180,180]
緯度: [-85.05112877980659,85.05112877980659] - 瓦片個數
層級0的瓦片數是1=2?0???2?0
層級1的瓦片數是4=2?1???210
層級n的瓦片數是2?n???2?n
如果只計算x軸或y軸一邊的瓦片數,就是2n個。
(1)OSM(OpenStreetMap)計算:
①四叉樹分割
n = 2zoom
Xtile = ((londeg + 180) / 360) * n
Ytile = (1 + log(tan(latrad) + sec(latrad)) / π) / 2 * n-1
Ytitle = (1 - log(tan(latrad) + 1/cos(latrad))/π) / 2 * n
②0級先二分,后四叉樹分割
n = 2 zoom+1
Xtile = ((londeg + 180) / 360) * n
Ytile =((latdeg + 90) / 360) *n
(2)google瓦片X編碼與tms一致,Y編碼關系為:
Ygoogle= 2zoom - YTMS-1
YGoogle + YTMS = 2zoom - 1
google地圖地址中的四個參數(x , y , z , s):
xy為切片坐標,z代表zoom,s =( 3x + y )%8
http://mt2.google.cn/vt/lyrs=m@167000000&hl=zh-CN&gl=cn&x=420&y=193&z=9&s=Galil
(3)轉換過程:經緯度 => 米 => 像素坐標 => 瓦片坐標
WebGIS 瓦片地圖引擎實現之——地圖瓦片加載計算原理介紹
①經緯度 => 米
②米 => 像素坐標
const MetersToPixelXY = (mx: number, my: number, zoom: number, tileSize = 256) => {// 地球的周長的一半 20037508.342789244 單位米const circumferenceHalf = Math.PI * 2 * 6378137 / 2.0 // 米/每像素const resolution = Math.PI * 2 * 6378137 / (tileSize * Math.pow(2, zoom)) const px = (mx + circumferenceHalf) / resolutionconst py = (my + circumferenceHalf) / resolution return { px, py } }③像素坐標 => 瓦片坐標
const PixelXYToTileXY = (px: number, py: number, tileSize = 256) => {const tx = Math.floor(px / tileSize);const ty = Math.floor(py / tileSize);return { tx, ty } }(4)經緯度與瓦片坐標互轉
①經緯度 => 瓦片
const LatLongToTileXY = (longitude: number, latitude: number, zoom: number) => {// 地球的周長的一半 20037508.342789244 單位米const circumferenceHalf = Math.PI * 6378137;const mx = (longitude * circumferenceHalf) / 180;const temp = Math.log(Math.tan((90 + latitude) * (Math.PI / 360.0))) / (Math.PI / 180.0);const my = (circumferenceHalf * temp) / 180;// 米/每像素const resolution = (Math.PI * 2 * 6378137) / (tileSize * Math.pow(2, zoom));// px = (mx + circumferenceHalf) / resolutionconst px = ((longitude + 180) / 360) * Math.pow(2, zoom) * tileSize;// py = (my + circumferenceHalf) / resolutionconst py = ((circumferenceHalf * temp) / 180 + circumferenceHalf) / (Math.PI * 2 * 6378137) / (tileSize * Math.pow(2, zoom));// tx = Math.floor(px / tileSize)const tx = Math.floor(((longitude + 180) / 360) * Math.pow(2, zoom));// ty = (1 - arsinh(tan(latitude * π / 180)) / π) * Math.pow(2, zoom)// 雙曲正弦函數 arsinh(x) => ln(x + (x^2 + 1)^0.5)const ty = Math.floor(((1 -Math.log(Math.tan((latitude * Math.PI) / 180) +1 / Math.cos((latitude * Math.PI) / 180)) /Math.PI) /2) *Math.pow(2, zoom));return { tx, ty }; };② 瓦片 => 經緯度
(5)屏幕點與經緯度轉換
const qreal PI = 3.14159265358979323846; const qreal deg2rad = PI / 180.0; const qreal rad2deg = 180.0 / PI; QPointF xxx::ll2qgs(const QPointF &ll, quint8 zoomLevel) const {const qreal tilesOnOneEdge = pow(2.0,zoomLevel);const quint16 tileSize = this->tileSize();qreal x = (ll.x() + 180) * (tilesOnOneEdge * tileSize) / 360; // coord to pixel!qreal y = (1 - (log(tan(PI/4 +(ll.y()*deg2rad)/2)) /PI)) /2 * (tilesOnOneEdge*tileSize);return QPoint(int(x), int(y)); }QPointF xxx::qgs2ll(const QPointF &qgs, quint8 zoomLevel) const {const qreal tilesOnOneEdge = pow(2.0,zoomLevel);const quint16 tileSize = this->tileSize();qreal longitude = (qgs.x() * (360 / (tilesOnOneEdge*tileSize))) - 180;qreal latitude = rad2deg*(atan(sinh((1-qgs.y()*(2/(tilesOnOneEdge*tileSize)))*PI)));return QPointF(longitude, latitude); }3、各大廠商瓦片編碼規范
- 谷歌XYZ:Z表示縮放層級,Z=zoom;XY的原點在左上角,X從左向右,Y從上向下。
- TMS:開源產品的標準,Z的定義與谷歌相同;XY的原點在左下角,X從左向右,Y從下向上。
- QuadTree:微軟Bing地圖使用的編碼規范,Z的定義與谷歌相同,同一層級的瓦片不用XY兩個維度表示,而只用一個整數表示,該整數服從四叉樹編碼規則
- 百度XYZ:Z從1開始,在最高級就把地圖分為四塊瓦片;XY的原點在經度為0緯度為0的位置,X從左向右,Y從下向上。
- 高德地圖:遵循谷歌XYZ的編碼規范,高德地圖范圍是85°N~85°S之間,x軸為85°N緯線,y軸為180°經線。
4、瓦片拼接
根據地理范圍換算出瓦片行列號的原理(核心)
瓦片計算
C#拼接地圖瓦片
(0)我的顯示步驟(在拖動或首次需顯示時)
- 根據中心點經緯度、等級、窗口寬高,計算出參與屏幕顯示的瓦片顯示列表(適當擴出去1個或2個)
- 根據“顯示列表”,查詢itemCashMap中是否存在對應Item,沒有則生成QGraphicsPixmap(或自定義類),指定位置并加入到itemCashMap中,并將多余的Item移出scene
- 根據中心點的視景坐標、屏幕大小,重新設置sceneRect
(1)流程
下圖中的2n為2n (n是level),r=6378137米,πr=20037508.3427892米
客戶端要想渲染出地圖有這么幾個簡單流程:
- 獲取當前地圖的地理范圍及當前地圖的縮放層級
- 通過上面兩個參數計算出所有瓦片坐標(瓦片索引)
- 通過瓦片的服務地址和瓦片坐標,拿到所有瓦片的數據(柵格數據或矢量數據,如果是柵格瓦片服務也就是圖片)
- 按順序拼接好瓦片渲染出來
(2)顯示瓦片
顯示瓦片地圖
用戶平移地圖的操作,就是改變地圖中心點經緯度坐標,重新計算要顯示的瓦片行列號,及第一張瓦片左上角相對于窗口的像素坐標值。
Web顯示瓦片
公里數、經緯度、窗口坐標、Scene坐標、view坐標
- 根據中心點經緯度、等級、窗口寬高,計算顯示范圍(公里數)
- 計算左上角瓦片XY
- 計算實際地理范圍
- 計算左上角偏移像素
- 計算瓦片個數
- 前端繪制瓦片
根據地理范圍換算出瓦片行列號的原理
5、Qt加載離線瓦片
(1)QGraphicsView方式
每一個瓦片表示為QGraphicsPixmapItem圖形項,通過設置圖形項在場景中位置來實現瓦片的拼接,從而展現一幅完整的地圖。
對于離線瓦片文件的加載可通過兩種方式實現:同步并行加載和異步多線程加載。
- 同步并行加載,基于QtCocurrent并行計算框架。同步并行加載的優點是運行平臺計算性能足夠時能為用戶提供極佳的地圖漫游和縮放體驗;缺點也相當明顯,當運行平臺計算性能不足時漫游和縮放可能出現卡頓。
- 異步多線程加載,基于線程池+任務隊列實現。運行平臺計算性能沒有過高的要求,不會出現界面卡頓現象;缺點是地圖漫游和縮放時,可能出現輕微閃爍影響用戶體驗。
(2)瓦片文件加載方式
由于瓦片是按 /zoom/x/y.png 的目錄結構組織瓦片數據,每次平移縮放加載數據時都要進行打開關閉文件的耗時操作,故可將所有瓦片打包到一個數據包中,這樣只執行一次打開文件操作。
(3)不同處理方式
實時更新:
漸近更新:
替換更新:
平面視角:
3D視角:
總結
- 上一篇: 大学计算机基础试题第一套,大学计算机基础
- 下一篇: 数独小解