Qt 插件綜合編程-基於插件的OpenStreetMap瓦片查看器client(1)-墨卡託投影與座標控制

(相關的代碼可以從https://github.com/goldenhawking/mercator.qtviewer.git直接克隆)
git

     咱們現在是準備作一個C/S架構的地圖顯示控件。就一定牽扯到座標系和UI的界面控制。github

一、墨卡託投影

    眼下osm採用墨卡託投影,這個投影的原理可以用一個假想實驗解釋。windows

     若是地球是一個透明的球體。在球體的球心有一個光源。咱們把一張幕布沿着赤道捲起來。使之與地球內切,地球上的一個點在這塊幕布上的投影就是其墨卡託投影位置。架構

上圖中,地球半徑是R=6378137米,可想而知,圓柱頂面周長爲  2 pi R。咱們以0度經線投影爲中軸,用剪刀沿着180度經線投影剪開,就能夠展開造成地圖面。這個地圖面的中心與地理位置 (0,0)重合;X軸是赤道,長度爲 2 * Pi * R,取值範圍 -pi R 到 pi R。即 -20037508 ~ + 20037508 米。Y軸是本初子午線投影。點光源直接照耀球迷質點造成的影子具備拉伸特性,高緯度地區拉伸很嚴重。其拉伸效果是 y = R ln (tan (pi/4 + lat/2)),在南極、北極存在奇點。框架

對通常的瓦片地圖而言,爲了方便計算機處理。通常y的取值範圍也是 -20037508 ~ + 20037508 米,反推回去,相應緯度範圍僅僅能表示到 -85度~85度。函數

二、墨卡託投影下的柵格化

     上面所說的墨卡託投影完畢了從地球上的一點到虛擬圓柱上一點的映射。然而,爲了使用計算機存儲、訪問地圖,就必須引入採樣。所謂的採樣,即便用離散的柵格像素表示連續的地理空間數據。咱們眼下所見的OpenStreetMap採用了19層比例尺,標號爲 0 ~ 18.post

     在0級,整個世界地圖被縮略爲一塊 256x256 的位圖。在1級。咱們把分辨率提升一倍。地圖由4塊256x256的瓦片組成;在二級,規模擴到 16塊,以此類推。下圖顯示的是這樣的層次關係:this


    可以簡單推算一下各級比例尺下,完整圖幅的大小。柵格化後的座標左上角是0,0,右下角是 size-1, size-1
spa

級別 瓦片行/列數 圖幅長/寬(size) 粗略像素分辨率
0 1 256 156千米
1 2 512 78千米
2 4 1024 39千米
3 8 2048 19千米
4 16 4096 9千米
5 32 8192 5千米
6 64 16384 2.5千米
7 128 32768 1.3千米
8 256 65536 611米
9 512 131072 305米
10 1024 262144 152米
11 2048 524288 76米
12 4096 1048576 38米
13 8192 2097152 19米
14 16384 4194304 9米
15 32768 8388608 4.5米
16 65536 16777216 2.2米
17 131072 33554432 1.1米
18 262144 67108864 0.5米

這些瓦片被編號爲行、列,加上比例尺,一個瓦片的索引即爲 (level,  x, y)。即比例尺、所在列號、所在行號。咱們僅僅要這三個參數,就能夠從openstreetmap瓦片server上下載瓦片位圖。插件

如:

http://c.tile.openstreetmap.org/0/0/0.png

http://c.tile.openstreetmap.org/2/2/1.png

需要注意的是。OSM瓦片server速度很是慢,當中國的鏡像位置有很多,比方

http://120.52.72.79/c.tile.openstreetmap.org/c3pr90ntcsf0/2/2/1.png

建議使用 FireFox 查看頁面元素,得到使用的瓦片真實地址。

三、爲視圖控制而準備的座標系統

     視圖在這裏可簡單理解爲一個窗體,具備有限的像素大小。

視圖控制包含顯示、漫遊、縮放等操做。這些操做的關鍵是從全局座標(瓦片墨卡托地圖)到視圖座標(通常左上角是0,0,右下角是 width-1。height-1) 的相互映射。

    咱們可以記錄當前窗體左上角、右下角的全局座標,從而實現窗體像素和全局像素的換算。然而,考慮到對於各個比例尺而言,圖幅是不斷變化的。且記錄左上角、右下角座標在比例尺變化後。相應的全局座標必須刷新。咱們決定不這麼作。

    可以採用更簡單的方式——記錄中心相對百分比座標和當前比例尺來實現一樣功能,進而,百分比做爲第一種全局座標系被創建起來,最好仍是稱之爲百分比座標 。

3.1 全局百分比座標

     百分比座標是一個等效的尺度無關座標。記錄了當前視圖中心位置相應的摩卡託座標百分比。

//Center Lat,Lon
double m_dCenterX;   //percentage, -0.5~0.5
double m_dCenterY;   //percentage, -0.5~0.5
int m_nLevel;        //0-18

在第一章的投影座標中。X.Y座標定義域均爲 [-piR , piR],而百分比座標即爲摩卡託座標與2piR的比值,記錄了當前中心實際偏離全圖中心的的比例,實質是歸一化。


設 px,py爲百分比座標, mx,my爲摩卡託投影座標。兩者關係爲

px = mx / 2piR

py = - my / 2piR

百分比座標的優勢是尺度無關。在各類比例尺下,一個固定的地理位置相應的百分比座標不變。

需要注意的是,百分比座標的Y軸取反。以便在興許轉換中與設備座標在度量、座標方向上取得一致。

百分比座標是一個浮點值。還沒法相應到當前比例尺圖幅上去。

咱們在第二章已經介紹了另一種全局座標系,即全局像素座標系。


3.2全局像素座標

     全局像素座標即當前比例尺下。一個位置相應的像素位置。第二章的表格裏。記錄了每個比例尺下的圖幅大小。這個座標就是地理位置相應當前比例尺圖幅上的像素點位置。當前圖幅左上角爲(0,0),右下角爲 (size-1, size-1)。 有了全局像素座標。就能夠計算需要的像素位於哪一個瓦片上。因爲所有的瓦片都是256x256大小。瓦片位置直接等於  Xw / 256, Yw/256。同一時候。基於3.1, 3.2的工做,依據當前窗體的尺寸,就能夠立馬計算窗體中隨意一點的全局像素座標。代碼是這種:

計算窗體位置(dX,dY)相應的全局像素座標(px,py)

	bool tilesviewer::CV_DP2World(qint32 dX, qint32 dY, double * px, double * py)
	{
		if (!px||!py)			return false;
		//!1.Current World Pixel Size, connected to nLevel
		int nCurrImgSize = (1<<m_nLevel)*256;
		//!2.current DP according to center
		double dx = dX-(width()/2.0);
		double dy = dY-(height()/2.0);
		//!3.Percentage -0.5 ~ 0.5 coord
		double dImgX = dx/nCurrImgSize+m_dCenterX;
		double dImgY = dy/nCurrImgSize+m_dCenterY;
		//!4.Calculat the World pixel coordinats
		*px = dImgX * nCurrImgSize + nCurrImgSize/2;
		*py = dImgY * nCurrImgSize + nCurrImgSize/2;
		return true;

	}

上圖中,黑色爲全局像素座標,紅色爲百分比座標,綠色爲窗體像素座標。


 

3.3 瓦片索引座標

   全局像素是由瓦片拼接而成的。咱們用3.2節的世界座標系可方便求取瓦片像素座標。

  瓦片行 = wy /256, 瓦片列 = wx /256

 瓦片像素: (wx % 256, wy %256)

上圖中。藍色爲瓦片座標與瓦片分割線。相應8x8。爲比例尺 3 時的情形。

四、視圖顯示

     有了上述幾種座標系,咱們可以爲用戶給定的 中心百分比座標 m_dCenterX, m_dCenterY,結合窗體大小。直接得到需要的瓦片索引。以及他們粘貼在當前視窗上的像素偏移。

	/*!
	 \brief When the tileviewer enter its paint_event function, this callback will be called.

	 \fn layer_tiles::cb_paintEvent
	 \param pImage	the In-mem image for paint .
	*/
	void layer_tiles::cb_paintEvent( QPainter * pPainter )
	{
		if (!m_pViewer || m_bVisible==false) return;
		//!1,We should first calculate current windows' position, centerx,centery, in pixcel.
		double nCenter_X ,nCenter_Y;
		//!2,if the CV_PercentageToPixel returns true, painting will begin.
		if (true==m_pViewer->CV_Pct2World(
					m_pViewer->centerX(),
					m_pViewer->centerY(),
					&nCenter_X,&nCenter_Y))
		{
			int sz_whole_idx = 1<<m_pViewer->level();
			//!2.1 get current center tile idx, in tile count.(tile is 256x256)
			int nCenX = nCenter_X/256;
			int nCenY = nCenter_Y/256;
			//!2.2 calculate current left top tile idx
			int nCurrLeftX = floor((nCenter_X-m_pViewer->width()/2)/256.0);
			int nCurrTopY = floor((nCenter_Y-m_pViewer->height()/2)/256.0);
			//!2.3 calculate current right bottom idx
			int nCurrRightX = ceil((nCenter_X+m_pViewer->width()/2)/256.0);
			int nCurrBottomY = ceil((nCenter_Y+m_pViewer->height()/2)/256.0);

			//!2.4 a repeat from tileindx left to right.
			for (int col = nCurrLeftX;col<=nCurrRightX;col++)
			{
				//!2.4.1 a repeat from tileindx top to bottom.
				for (int row = nCurrTopY;row<=nCurrBottomY;row++)
				{
					QImage image_source;
					int req_row = row, req_col = col;
					if (row<0 || row>=sz_whole_idx)
						continue;
					if (col>=sz_whole_idx)
						req_col = col % sz_whole_idx;
					if (col<0)
						req_col = (col + (1-col/sz_whole_idx)*sz_whole_idx) % sz_whole_idx;
					//!2.4.2 call getTileImage to query the image .
					if (true==this->getTileImage(m_pViewer->level(),req_col,req_row,image_source))
					{
						//bitblt
						int nTileOffX = (col-nCenX)*256;
						int nTileOffY = (row-nCenY)*256;
						//0,0 lefttop offset
						int zero_offX = int(nCenter_X+0.5) % 256;
						int zero_offY = int(nCenter_Y+0.5) % 256;
						//bitblt cood
						int tar_x = m_pViewer->width()/2-zero_offX+nTileOffX;
						int tar_y = m_pViewer->height()/2-zero_offY+nTileOffY;
						//bitblt
						pPainter->drawImage(tar_x,tar_y,image_source);
					}
				}
			}
		}

	}

這裏一個關鍵的座標轉換函數是 CV_Pct2World, 其功能是把給定的百分比座標換算爲世界像素座標。

五、拖動、漫遊、縮放

     拖動、漫遊相應的是鼠標消息。鼠標消息中的座標全部都是視窗像素。咱們僅僅要把視窗像素換算爲百分比,把音響施加到中心座標下,就能夠完畢動做。

     縮放是指改變比例尺 m_nLevel,無需別的操做。m_nLevel改變後,立馬重繪窗體,一切皆本身主動計算——這得益於咱們控制視圖的參數是尺度無關的歸一化座標。

    咱們以拖動爲例,   首先,在鼠標按鍵按下時。記錄起始位置:

    見bool layer_tiles::cb_mousePressEvent(QMouseEvent*event)

		if (event->button()==Qt::LeftButton)
		{
			this->m_nStartPosX = event->pos().x();
			this->m_nStartPosY = event->pos().y();
		}

然後,在鼠標彈起時。記錄結束位置並換算, 見 bool layer_tiles :: cb_mouseReleaseEvent ( QMouseEvent * event ):

		if (event->button()==Qt::LeftButton)
		{
			int nOffsetX = event->pos().x()-this->m_nStartPosX;
			int nOffsetY = event->pos().y()-this->m_nStartPosY;
			if (!(nOffsetX ==0 && nOffsetY==0))
			{
				m_pViewer->DragView(nOffsetX,nOffsetY);
				this->m_nStartPosX = this->m_nStartPosY = -1;
				res = true;
			}
		}

上面代碼中的 nOffsetX,nOffsetY便是拖動的屏幕像素距離。這個拖動參數被傳給了 void tilesviewer :: DragView ( int nOffsetX , int nOffsetY )
	void tilesviewer::DragView(int nOffsetX,int nOffsetY)
	{
		if (nOffsetX==0 && nOffsetY == 0)
			return;

		int sz_whole_idx = 1<<m_nLevel;
		int sz_whole_size = sz_whole_idx*256;

		double dx = nOffsetX*1.0/sz_whole_size;
		double dy = nOffsetY*1.0/sz_whole_size;

		this->m_dCenterX -= dx;
		this->m_dCenterY -= dy;

	

終於,當前中心的百分比被刷新。

小結

     本章介紹了視圖的控制。爲了簡單方便,咱們創建了一個百分比座標系。歸一化的參數避免在縮放過程當中改動視窗的全局座標,且很便於計算。

固然,上述座標系僅僅是顯示瓦片需要的座標系。假設還要和經緯度打交道,那就必須引入經緯度座標、墨卡託座標。做爲一個插件化的project,咱們但願這些座標轉化全部由主框架公佈功能,供插件使用,在下一章節,咱們就介紹基於Qt插件的圖層架構設計。

相關文章
相關標籤/搜索