一步步DIY: OSM-Web服务器(六) C/S架构客户端开发中的细节问题
虽然Ajax的Web应用功能强大,但是,很多时候还是需要 C/S模式的客户端程序。最为典型的应用是为现有产品添加新的OSM地图支持(比如替换掉MapX)。很多现有GIS应用都是Native C++的。这些CLient 与网页最大的不同,就是需要即时以及复杂的交互。以OSM为底图,其上需要进行复杂的科学计算,呈现一些网页不容易表现的功能。因此,在NATIVE C++上做一个地图控件是最合适的。
<1> 坐标系统
地图控件本质上是一个窗口(Widget),设计这种控件,最细节、最关键的问题就是坐标转换。对摩卡托投影系的OSM地图而言,由于其比例尺是成倍阶跃的,不存在无级缩放、无缝漫游的要求,设计起来相对简单。
控制当前视图的要素有两个就够了, 一个是比例尺(0-18,整形),一个是视图中心点相对全图的行、列百分比(0.0-1.0)。有了这两个要素,立刻可以计算出需要哪些瓦片来填补背景。
首先,对比例尺 n 来说,图幅长、宽都是 2^(n+8) 像素, n=0 时,就是 256 *256, n=1为 512 * 512。当然,瓦片的行列容积都是 2^n,即 n=0 时就是 1x1,n=1时为 2x2,n=2时为 4*4,以此类推。通过百分比中心点,即可知道中心点位于当前图幅的像素位置:
x = cx * (2^(n+8))
y = cy * (2^(n+8))
同时,知道了中心点的瓦片编号。由于瓦片都是 256 *256 的,则
nx = floor(x /256)
ny = floor(y/256)
而贴图的偏移为
ox = x mod 256
oy = y mod 256
当然了,具体的贴图还要看窗口坐标的轴方向、窗口坐标的原点。但原理是一样的。按照瓦片的坐标偏移,把略微大于视图范围的各个瓦片顺序读出来,表在底图的缓存里,就完成拼接了。
其次,对用户拖动来说,屏幕上像素的拖拽偏移 dx, dy 要换算到归一化的 0-1 全图坐标上。这一步原理很简单。由于比例尺已知,图幅大小已知,比例尺n下,用户拖拽了 dx,dy 像素,相当于整个视图中心移动了
dcx = dx / (2^(n+8))
dcy = dy / (2^(n+8))
至于说符号问题,就是向左为正还是向右为正,还要看屏幕坐标系的朝向。
<2> 与 WGS 84 的转换
第一步里,所有坐标均是与摩卡托线形相关的。但是,与外部程序接口,我们一般用经纬度,这样,需要转换。摩卡托与经纬度的转换,可以看看wiki,里给出转换的类:
4.1 视图控制
其主要的控制变量为三个
bool tilesviewer::oTVP_LLA2DP(double lat,double lon,qint32 * pX,qint32 *pY){if (!pX||!pY)return false;//到墨卡托投影double dMx = cProjectionMercator(lat,lon).ToMercator().m_x;double dMy = cProjectionMercator(lat,lon).ToMercator().m_y;//计算巨幅图片内的百分比double dperx = dMx/(cProjectionMercator::pi*cProjectionMercator::R*2);double dpery = -dMy/(cProjectionMercator::pi*cProjectionMercator::R*2);double dCurrImgSize = pow(2.0,m_nLevel)*256;//计算要转换的点的巨幅图像坐标double dTarX = dperx * dCurrImgSize + dCurrImgSize/2;double dTarY = dpery * dCurrImgSize + dCurrImgSize/2;//计算当前中心点的巨幅图像坐标double dCurrX = dCurrImgSize*m_dCenterX+dCurrImgSize/2;double dCurrY = dCurrImgSize*m_dCenterY+dCurrImgSize/2;//计算当前中心的全局坐标double nOffsetLT_x = (dCurrX-width()/2.0);double nOffsetLT_y = (dCurrY-height()/2.0);//判断是否在视点内*pX = dTarX - nOffsetLT_x+.5;*pY = dTarY - nOffsetLT_y+.5;if (*pX>=0 && *pX<width()&&*pY>=0&&*pY<height())return true;return false;}bool tilesviewer::oTVP_DP2LLA(qint32 X,qint32 Y,double * plat,double * plon){if (!plat||!plon)return false;//显示经纬度//当前缩放图幅的像素数double dCurrImgSize = pow(2.0,m_nLevel)*256;int dx = X-(width()/2+.5);int dy = Y-(height()/2+.5);double dImgX = dx/dCurrImgSize+m_dCenterX;double dImgY = dy/dCurrImgSize+m_dCenterY;double Mercator_x = cProjectionMercator::pi*cProjectionMercator::R*2*dImgX;double Mercator_y = -cProjectionMercator::pi*cProjectionMercator::R*2*dImgY;double dLat = cProjectionMercator(Mercator_y,Mercator_x).ToLatLon().m_lat;double dLon = cProjectionMercator(Mercator_y,Mercator_x).ToLatLon().m_lon;if (dLat>=-90 && dLat<=90 && dLon>=-180 && dLon<=180){*plat = dLat;*plon = dLon;return true;}return false;}
<5> 后续功能上述实现的是最简单的单线程客户端。在局域网上,问题不大,如果拿到因特网,就要按照更高要求写多线程、本地缓存了。另外,一个底图并不是目的,目的是让其上的各类应用能够方便的搭建。这要求要向用户提供二次开发的支持。可以采用的比如 QT的插件、ActiveX控件等等。这些东西都有设计模式可以参考,大可以自由发挥啦!
------------------------------------
后记--
2008年,偶然机会接触OSM到现在,其在相关的专业领域发挥了非常大的作用。OSM 作为完全开放的地理信息解决方案,还没有形成ArcGIS那样方便的成套的二次开发环境,但是其丰富的数据本身就是最强大的优势,不断更新的数据使他充满活力。相信大家都期待它的进步,开放的力量是无穷的!今后还会继续跟进OSM的应用。