最小GIS迷你地球实现(实现一套最小的三维GIS球体) V1.0.0.0版本
数据加代码比较大(主要是数据,数据有1G多,代码约5000行),无法上传,如需要微信联系(17381925156)
效果图:
文章来源:https://www.toymoban.com/news/detail-710836.html
相机推进后:
1 . 功能目标
1.1 实现基本的卫片数据浏览
1.2 实现高程数据的浏览
1.3 实现基本的三维操作,平移,旋转缩放功能
2 必备知识
2.1 图形学基础(OpenGL基础知识),采用OpenGL3.0标准,纹理数组,shader,Instance绘制
2.2 C++编程基础,vector,list,string,map,function,sort,c++ 17,匿名函数(lamda表达式),智能指针
2.3 多线程支持,多线程采用生产者与消费者模型,信号量,互斥体,队列
2.4 地理信息基本支持,卫片,高程,投影,转换,坐标系
3 软件结构图
3.1 场景管理
3.1.1 相机实现
实现第一人称相机,主要是实现功能,
定点旋转
virtual void rotateViewXByCenter(real angle,real3 pos) { if (!(_flag & FLAG_ROT_X)) { return; } //! 计算眼睛到鼠标点的方向 real3 vDir = pos - _eye; real len1 = length(vDir); real len = 0; vDir = normalize(vDir); matrix4r mat(1); mat.rotate(angle, _right); vDir = vDir * mat; _eye = pos - vDir * len1; _dir = _dir * mat; _up = _up * mat; #ifndef COORD_LH _right = CELL::normalize(cross(_dir,_up)); _up = CELL::normalize(cross(_right,_dir)); #else _right = CELL::normalize(cross(_up,_dir)); _up = CELL::normalize(cross(_dir,_right)); #endif len = CELL::length(_eye - _target); _target = _eye + _dir * len; update(); }
定点缩放
/** * 指定点推进摄像机 */ virtual void scaleCameraByPos(const real3& pos,real persent) { real3 dir = CELL::normalize(pos - _eye); real dis = CELL::length(pos - _eye) * persent; real disCam = CELL::length(_target - _eye) * persent; real3 dirCam = CELL::normalize(_target - _eye); _eye = pos - dir * dis; _target = _eye + dirCam * disCam; update(); }
精确移动
virtual void moveLeft(real fElapsed) { _eye -= normalize(_right) * _speed * fElapsed; _target -= normalize(_right) * _speed * fElapsed; } virtual void moveRight(real fElapsed) { _eye += normalize(_right) * _speed * fElapsed; _target += normalize(_right) * _speed * fElapsed; } virtual void moveFront(real fElapsed) { _eye += _dir * _speed * fElapsed; _target += _dir * _speed * fElapsed; } virtual void moveBack(real fElapsed) { _eye -= _dir * _speed * fElapsed; _target -= _dir * _speed * fElapsed; } /** * 往上看,则向下移动摄像机 */ virtual void moveUp(real fElapsed) { _eye += _up * _speed * fElapsed; _target += _up * _speed * fElapsed; } /** * 向下看,则摄像机向上移动 */ virtual void moveDown(real fElapsed) { _eye -= _up * _speed * fElapsed; _target -= _up * _speed * fElapsed; } /** * 根据给定的方向移动摄像机 */ virtual void moveDir(real3 dir,real fElapsed) { _eye += dir * _speed * fElapsed; _target += dir * _speed * fElapsed; }
实现屏幕到世界坐标投影转换
/** * 窗口坐标转化为世界坐标 */ real3 screenToWorld(const real2& screen) { real4 screens(screen.x,screen.y,0,1); real4 world; unProject(screens,world); return real3(world.x,world.y,world.z); }
世界坐标到屏幕坐标投影转换
/** * 世界坐标转化为窗口坐标 */ real2 worldToScreen( const real3& world) { real4 worlds(world.x,world.y,world.z,1); real4 screens; project(worlds,screens); return real2(screens.x,screens.y); }
根据屏幕坐标点创建射线
Ray createRayFromScreen(int x,int y) { real4 minWorld; real4 maxWorld; real4 screen(real(x),real(y),0,1); real4 screen1(real(x),real(y),1,1); unProject(screen,minWorld); unProject(screen1,maxWorld); Ray ray; ray.setOrigin(real3(minWorld.x,minWorld.y,minWorld.z)); real3 dir(maxWorld.x - minWorld.x,maxWorld.y - minWorld.y, maxWorld.z - minWorld.z); ray.setDirection(normalize(dir)); return ray; }
3.1.2 裁剪实现
根据投影矩阵,观察矩阵构造视锥
1. 支持判断点是否在视锥内
2. 支持判断是否球体在视锥内
3. 支持判断包围盒是否在视锥内。
template<class T> class tfrustum { public: enum { FRUSTUM_LEFT = 0, FRUSTUM_RIGHT = 1, FRUSTUM_TOP = 2, FRUSTUM_BOTTOM = 3, FRUSTUM_FAR = 4, FRUSTUM_NEAR = 5, }; public: /** * project * modleview */ void loadFrustum(const tmat4x4<T> &mvp) { const T* dataPtr = mvp.data(); _planes[FRUSTUM_LEFT ] = Plane<T>(dataPtr[12] - dataPtr[0], dataPtr[13] - dataPtr[1], dataPtr[14] - dataPtr[2], dataPtr[15] - dataPtr[3]); _planes[FRUSTUM_RIGHT ] = Plane<T>(dataPtr[12] + dataPtr[0], dataPtr[13] + dataPtr[1], dataPtr[14] + dataPtr[2], dataPtr[15] + dataPtr[3]); _planes[FRUSTUM_TOP ] = Plane<T>(dataPtr[12] - dataPtr[4], dataPtr[13] - dataPtr[5], dataPtr[14] - dataPtr[6], dataPtr[15] - dataPtr[7]); _planes[FRUSTUM_BOTTOM] = Plane<T>(dataPtr[12] + dataPtr[4], dataPtr[13] + dataPtr[5], dataPtr[14] + dataPtr[6], dataPtr[15] + dataPtr[7]); _planes[FRUSTUM_FAR ] = Plane<T>(dataPtr[12] - dataPtr[8], dataPtr[13] - dataPtr[9], dataPtr[14] - dataPtr[10], dataPtr[15] - dataPtr[11]); _planes[FRUSTUM_NEAR ] = Plane<T>(dataPtr[12] + dataPtr[8], dataPtr[13] + dataPtr[9], dataPtr[14] + dataPtr[10], dataPtr[15] + dataPtr[11]); } bool pointInFrustum(const tvec3<T> &pos) const { for (int i = 0; i < 6; i++) { if (_planes[i].distance(pos) <= 0) return false; } return true; } bool sphereInFrustum(const tvec3<T> &pos, const float radius) const { for (int i = 0; i < 6; i++) { if (_planes[i].distance(pos) <= -radius) return false; } return true; } bool cubeInFrustum(T minX,T maxX,T minY,T maxY,T minZ,T maxZ) const { for (int i = 0; i < 6; i++) { if (_planes[i].distance(tvec3<T>(minX, minY, minZ)) > 0) continue; if (_planes[i].distance(tvec3<T>(minX, minY, maxZ)) > 0) continue; if (_planes[i].distance(tvec3<T>(minX, maxY, minZ)) > 0) continue; if (_planes[i].distance(tvec3<T>(minX, maxY, maxZ)) > 0) continue; if (_planes[i].distance(tvec3<T>(maxX, minY, minZ)) > 0) continue; if (_planes[i].distance(tvec3<T>(maxX, minY, maxZ)) > 0) continue; if (_planes[i].distance(tvec3<T>(maxX, maxY, minZ)) > 0) continue; if (_planes[i].distance(tvec3<T>(maxX, maxY, maxZ)) > 0) continue; return false; } return true; } const Plane<T> &getPlane(const int plane) const { return _planes[plane]; } public: Plane<T> _planes[6]; };
3.1.2 浏览实现
输入输出接口定义,实现对鼠标键盘事件的抽象,后续可以实现跨平台处理
class CELLInput { public: /// <summary> /// 鼠标左键按下 /// </summary> virtual void onLButtonDown(int x,int y) = 0; /// <summary> /// 鼠标左键提起 /// </summary> virtual void onLButtonUp(int x, int y) = 0; virtual void onLButtonDbClick(int x, int y) = 0; virtual void onRButtonDbClick(int x, int y) = 0; virtual void onMButtonDbClick(int x, int y) = 0; /// <summary> /// 鼠标移动事件 /// </summary> virtual void onRButtonDown(int x, int y) = 0; /// <summary> /// 鼠标移动事件 /// </summary> virtual void onRButtonUp(int x, int y) = 0; /// <summary> /// 鼠标移动事件 /// </summary> virtual void onMButtonDown(int x, int y) = 0; /// <summary> /// 鼠标移动事件 /// </summary> virtual void onMButtonUp(int x, int y) = 0; /// <summary> /// 鼠标移动事件 /// </summary> virtual void onMouseMove(int x,int y) = 0; /// <summary> /// 鼠标移动事件 /// </summary> virtual void onMouseWheel(int delta) = 0; /// <summary> /// 键盘事件 /// </summary> virtual void onKeyDown(int key) = 0; /// <summary> /// 键盘事件 /// </summary> virtual void onKeyUp(int key) = 0; };
具体业务代码实现:处理windows事件
LRESULT eventProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_LBUTTONDOWN: _frame->onLButtonDown(LOWORD(lParam),HIWORD(lParam)); break; case WM_LBUTTONUP: _frame->onLButtonUp(LOWORD(lParam),HIWORD(lParam)); break; case WM_RBUTTONDOWN: _frame->onRButtonDown(LOWORD(lParam),HIWORD(lParam)); break; case WM_RBUTTONUP: _frame->onRButtonUp(LOWORD(lParam), HIWORD(lParam)); break; case WM_MBUTTONDOWN: _frame->onMButtonDown(LOWORD(lParam), HIWORD(lParam)); break; case WM_MBUTTONUP: _frame->onMButtonUp(LOWORD(lParam), HIWORD(lParam)); break; case WM_MOUSEMOVE: _context._mouseX = LOWORD(lParam); _context._mouseY = HIWORD(lParam); _frame->onMouseMove(LOWORD(lParam), HIWORD(lParam)); break; case WM_MOUSEWHEEL: _frame->onMouseWheel(GET_WHEEL_DELTA_WPARAM(wParam)); break; case WM_KEYDOWN: _frame->onKeyDown((int)wParam); break; case WM_KEYUP: _frame->onKeyUp((int)wParam); break; case WM_LBUTTONDBLCLK: _frame->onLButtonDbClick(LOWORD(lParam), HIWORD(lParam)); break; case WM_RBUTTONDBLCLK: _frame->onLButtonDbClick(LOWORD(lParam), HIWORD(lParam)); break; case WM_MBUTTONDBLCLK: _frame->onMButtonDbClick(LOWORD(lParam), HIWORD(lParam)); break; case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, &ps); EndPaint(hWnd, &ps); } break; case WM_SIZE: { if(IsWindow(_hWnd)) { RECT rect; GetClientRect(_hWnd,&rect); _context._width = rect.right - rect.left; _context._height = rect.bottom - rect.top; } } break; case WM_DESTROY: _threadRun = false; CELLThread::join(); PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return S_OK;
具体动作实现:
void CELLFrameBigMap::update(CELLContext& ) { _context._device->setViewPort(0,0,_context._width,_context._height); _context._screenPrj = CELL::ortho<real>(0.0f,(real)_context._width,(real)_context._height,0,-1000.0f,1000.0f); _context._camera.setViewSize(real2(_context._width,_context._height)); _context._camera.perspective(45.0,real(_context._width)/real(_context._height),10.0,FSIZE * 10); _context._camera.update(); _context._mvp = _context._camera._matProj * _context._camera._matView * _context._camera._matWorld ; _context._vp = _context._camera._matProj * _context._camera._matView; _context._timePerFrame = (float)_timeStamp.getElapsedSecond(); _timeStamp.update(); matrix4r matVP = _context._vp.transpose(); _context._frustum.loadFrustum(matVP); if (_context._keyState[VK_UP]) { _context._camera.moveFront(_context._timePerFrame); } if (_context._keyState[VK_DOWN]) { _context._camera.moveBack(_context._timePerFrame); } if (_context._keyState[VK_LEFT]) { _context._camera.moveLeft(_context._timePerFrame); } if (_context._keyState[VK_RIGHT]) { _context._camera.moveRight(_context._timePerFrame); } _terrain.update(_context); } void CELLFrameBigMap::onFrameStart(CELLContext& context) { context._device->clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); context._device->clearColor(0, 0, 0, 1); context._device->disableRenderState(GL_CULL_FACE); context._device->enableRenderState(GL_DEPTH_TEST); _terrain.render(context); } void CELLFrameBigMap::onFrameEnd(CELLContext& context) { } void CELLFrameBigMap::onLButtonDown(int x, int y) { getPointsFromScreen(x,y,_basePoint); _bLbuttonDown = true; _lbuttonDown = int2(x,y); } void CELLFrameBigMap::onLButtonUp(int x, int y) { _lbuttonDown = int2(x,y); _bLbuttonDown = false; } void CELLFrameBigMap::onLButtonDbClick(int x, int y) { /// _basePoint real3 vDir = CELL::normalize(-_basePoint); real dist = CELL::length(_context._camera.getEye() - _context._camera.getTarget()); _context._camera.setTarget(_basePoint); real3 vEye = _basePoint - dist * vDir; _context._camera.setEye(vEye); _context._camera.calcDir(); _context._camera.rotateViewXByCenter(0,_basePoint); _context._camera.update(); } void CELLFrameBigMap::onRButtonDbClick(int x, int y) { } void CELLFrameBigMap::onMButtonDbClick(int x, int y) { } void CELLFrameBigMap::onRButtonDown(int , int ) { } void CELLFrameBigMap::onRButtonUp(int , int ) { } void CELLFrameBigMap::onMButtonDown(int x, int y) { _mButtonDown = int2(x,y); _bMButtonDown = true; } void CELLFrameBigMap::onMButtonUp(int x, int y) { _mButtonDown = int2(x,y); _bMButtonDown = false; } void CELLFrameBigMap::onMouseMove(int x, int y) { if (_bLbuttonDown) { int2 curPoint(x, y); int2 offset = curPoint - _lbuttonDown; _lbuttonDown = curPoint; _context._camera.rotateViewYByCenter(offset.x, _basePoint); _context._camera.rotateViewXByCenter(offset.y, _basePoint); } if (_bMButtonDown) { int2 ofScreen = int2(x,y) - _mButtonDown; _mButtonDown = int2(x,y); moveScene(_basePoint,ofScreen); } } void CELLFrameBigMap::onMouseWheel(int delta) { double persent = 1; if (delta > 0) { persent = 0.9; } else { persent = 1.1; } _context._camera.scaleCameraByPos(_basePoint,persent); }
3.2 投影管理
3.2.1 墨卡托投影实现
定义
Web Mercator 坐标系使用的投影方法不是严格意义的墨卡托投影,而是一个被 EPSG(European Petroleum Survey Group)
称为伪墨卡托的投影方法,这个伪墨卡托投影方法的大名是 Popular Visualization Pseudo Mercator,PVPM。因为这个坐标
系统是 Google Map 最先使用的,或者更确切地说,是Google 最先发明的。在投影过程中,将表示地球的参考椭球体近似的
作为正球体处理(正球体半径 R = 椭球体半长轴 a)。后来,Web Mercator 在 Web 地图领域被广泛使用,这个坐标系就名声
大噪。尽管这个坐标系由于精度问题一度不被GIS专业人士接受,但最终 EPSG 还是给了 WKID:3857。以整个世界范围,
赤道作为标准纬线,本初子午线作为中央经线,两者交点为坐标原点,向东向北为正,向西向南为负。
X轴:由于赤道半径为6378137米,则赤道周长为2PIr = 20037508.3427892,因此X轴的取值范围:[-20037508.3427892,20037508.3427892]。
Y轴:由墨卡托投影的公式可知,当纬度φ接近90°时,y值趋向于无穷。
为了方便实现一级地图在长度上一分为二,也就是在高一级精度上用4张切片地图显示,最好将xy轴显示的取值范围统一,
为此Y轴的取值范围也限定在[-20037508.3427892,20037508.3427892]之间。
通过计算纬度最大值就是85.0511287798066°
另外值得一提的是,现在网上大部分地图瓦片瓦片都是基于Web墨卡托的,就是第0层1一张图片,第1层2*2张图片...,如果使用OpenLayers可以很好的调用公共服务器
编号
EPSG:3857
EPSG:102100
EPSG:900913 (谷歌自己起的,没有被EPSG接受,900913=google)
转换实现
1 class GlobalMercator 2 { 3 protected: 4 int tileSize ; 5 real initialResolution; 6 real originShift; 7 8 public: 9 GlobalMercator() 10 { 11 tileSize = 256; 12 initialResolution = 2 * PI * 6378137 / tileSize; 13 originShift = 2 * PI * 6378137 / 2.0; 14 } 15 16 real2 LatLonToMeters(real lat, real lon ) 17 { 18 //"Converts given lat/lon in WGS84 Datum to XY in Spherical Mercator EPSG:900913" 19 real mx = lon * originShift / 180.0 ; 20 real my = log( tan((90 + lat) *PI / 360.0 )) / (PI / 180.0); 21 my = my * originShift / 180.0; 22 return real2(mx, my); 23 } 24 25 real2 MetersToLatLon(int mx,int my ) 26 { 27 real lon = (mx / originShift) * 180.0; 28 real lat = (my / originShift) * 180.0; 29 lat = real(180 / PI * (2 * atan( exp( lat * PI / 180.0)) - PI / 2.0)); 30 return real2(lon,lat); 31 } 32 33 real resolution(int zoom ) 34 { 35 // return (2 * math.pi * 6378137) / (self.tileSize * 2**zoom) 36 return initialResolution / (pow(2,real(zoom))); 37 } 38 real2 PixelsToMeters(int px, int py, int zoom) 39 { 40 real res = resolution( zoom ); 41 real mx = px * res - originShift; 42 real my = py * res - originShift; 43 return real2(mx,my); 44 } 45 int2 MetersToPixels(real mx, real my, int zoom) 46 { 47 real res = resolution( zoom ); 48 int px = int((mx + originShift) / res) ; 49 int py = int((my + originShift) / res); 50 return int2(px,py); 51 } 52 53 int2 PixelsToTile(int px, int py) 54 { 55 int tx = int( ceil( px / real(tileSize) ) - 1 ); 56 int ty = int( ceil( py / real(tileSize) ) - 1 ); 57 return int2(tx,ty); 58 } 59 60 int2 MetersToTile(real mx, real my, int zoom) 61 { 62 int2 vPt = MetersToPixels( mx, my, zoom); 63 return PixelsToTile(vPt.x,vPt.y) ; 64 } 65 int2 LongLatToTile(real dLon,real dLat,int zoom) 66 { 67 real2 vMeter = LatLonToMeters(dLat,dLon); 68 69 return MetersToTile(vMeter.x,vMeter.y,zoom); 70 } 71 72 };
1 class CELLMercator :public CELLSpRef 2 { 3 protected: 4 GlobalMercator _proj; 5 public: 6 7 CELLMercator(void) 8 { 9 10 } 11 12 ~CELLMercator(void) 13 { 14 } 15 /** 16 * 将经度转化为x-tile key 17 */ 18 static int long2tilex(real lon, int z) 19 { 20 return (int)(floor((lon + 180.0) / 360.0 * pow(2.0, z))); 21 } 22 /** 23 * 将纬度转化为y-tile key 24 */ 25 static int lat2tiley(real lat, int z) 26 { 27 return (int)(floor((1.0 - log( tan(lat * PI/180.0) + 1.0 / cos(lat * PI/180.0)) / PI) / 2.0 * pow(2.0, z))); 28 } 29 /** 30 * 给定 x-tile 获取经度 31 */ 32 static real tilex2long(int x, int z) 33 { 34 return x / pow(2.0, z) * 360.0 - 180; 35 } 36 /** 37 * 给定 y-tile 获取纬度 38 */ 39 static real tiley2lat(int y, int z) 40 { 41 real n = PI - 2.0 * PI * y / pow(2.0, z); 42 return 180.0 / PI * atan(0.5 * (exp(n) - exp(-n))); 43 } 44 45 static real2 tile2lonlat(int x,int y, int z) 46 { 47 real p = pow(2.0, z); 48 real n = PI - 2.0 * PI * y / p; 49 real lat = 180.0 / PI * atan(0.5 * (exp(n) - exp(-n))); 50 real lon = x / p * 360.0 - 180; 51 return real2(lon,lat); 52 } 53 real2 tile2World(int x,int y, int z) 54 { 55 real2 lonLat = tile2lonlat(x,y,z); 56 return _proj.LatLonToMeters(lonLat.x,lonLat.y); 57 } 58 /** 59 * 根据经纬度与级别返回瓦片Key 60 */ 61 virtual int3 getKey(unsigned level, real rLong,real rLat) 62 { 63 //! 注意,这里不做显示,是为了处理地图滚动,即地图的显示范围可以不进行限制 64 #if 1 65 if (rLong <= -180) 66 { 67 rLong = -179.9; 68 } 69 if (rLong >= 180) 70 { 71 rLong = 179.9; 72 } 73 if (rLat < -85) 74 { 75 rLat = -85; 76 } 77 if (rLat > 85) 78 { 79 rLat = 85; 80 } 81 #endif 82 int levelTileNumber = 0; 83 levelTileNumber = 1<<level; 84 85 int xTile = long2tilex(rLong,level); 86 87 int yTile = lat2tiley(rLat,level); 88 89 return int3(xTile,yTile,level); 90 } 91 public: 92 /** 93 * 经纬度转化为世界坐标 94 */ 95 virtual real3 longLatToWorld(const real3& longLatx) override 96 { 97 //return longLatx; 98 real3 longLat = longLatx; 99 longLat.x = tmin<real>(179.9999f,longLatx.x); 100 longLat.x = tmax<real>(-179.9999f,longLat.x); 101 102 longLat.y = tmin<real>(85.0f,longLatx.y); 103 longLat.y = tmax<real>(-85.0f,longLat.y); 104 auto res = _proj.LatLonToMeters(longLatx.y,longLatx.x); 105 return real3(res.x,0,res.y); 106 } 107 108 /** 109 * 世界坐标转化为经纬度 110 */ 111 virtual real3 worldToLongLat(const real3& world) override 112 { 113 const real worldMin = -20037504.0f * 1000; 114 const real worldMax = 20037504.0f * 1000; 115 real dWordX = (real)tmin<real>(world.x, worldMax); 116 dWordX = (real)tmax<real>((real)dWordX, worldMin); 117 118 real dWordY = (real)tmin<real>(world.y, worldMax); 119 dWordY = (real)tmax<real>((real)dWordY, worldMin); 120 auto res = _proj.MetersToLatLon((int)dWordX, (int)dWordY); 121 return real3(res.x,res.y,0); 122 } 123 124 /** 125 * 得到当前级别下tile的个数 126 */ 127 int getTileNumber(int lev) 128 { 129 return (int)pow(2,real(lev)); 130 } 131 };
3.2.2 wgs84投影实现
定义
WGS的最新版本为WGS 84(也称作WGS 1984、EPSG:4326),1984年定义、最后修订于2004年。
投影过程如下图所示:
球体被切成一个个小圆弧,一共60个投影带,分别为01,02.........60
分为60个投影带,每一个投影带投影通用编码分别为:32601,32650,32660,从上图我们可以看到从南到北,
同一个投影带又被划分为分别为X,W,V等小格子:在同一个投影带内,比如50投影带内,通用编码为32650,依次又形成了,
50X,50W,50V,50S等小格子
代码实现
1 class CELLWgs842d :public CELLSpRef 2 { 3 public: 4 5 /** 6 * 经纬度转化为世界坐标 7 */ 8 virtual real3 longLatToWorld(const real3& longLatx) override 9 { 10 real3 world; 11 world.x = longLatx.x * WGS_84_RADIUS_EQUATOR; 12 world.y = longLatx.z; 13 world.z = longLatx.y * WGS_84_RADIUS_EQUATOR; 14 return world; 15 } 16 17 /** 18 * 世界坐标转化为经纬度 19 */ 20 virtual real3 worldToLongLat(const real3& world) override 21 { 22 real3 lonlat; 23 lonlat.x = world.x / WGS_84_RADIUS_EQUATOR; 24 lonlat.y = world.y / WGS_84_RADIUS_EQUATOR; 25 lonlat.y = world.z; 26 return lonlat; 27 } 28 29 /** 30 * 根据经纬度与级别返回瓦片Key 31 */ 32 virtual int3 getKey(unsigned level, real rLong,real rLat) override 33 { 34 unsigned xTiles = 2<<level; 35 unsigned yTiles = 1<<level; 36 double xWidth = TWO_PI / xTiles; 37 double yHeight = PI / yTiles; 38 double xCoord = (rLong + PI) / xWidth; 39 if (xCoord >= xTiles) 40 { 41 xCoord = xTiles - 1; 42 } 43 44 double yCoord = (HALF_PI - rLat) / yHeight; 45 if (yCoord >= yTiles) 46 { 47 yCoord = yTiles - 1; 48 } 49 50 return int3(int(xCoord),int(yCoord),level); 51 } 52 };
3.3.2 wgs84球体投影实现
定义:
实现
1 class CELLWgs84Sphere :public CELLSpRef 2 { 3 protected: 4 tellipsoidModelIn<double> _spr; 5 public: 6 7 /** 8 * 经纬度转化为世界坐标 9 */ 10 virtual real3 longLatToWorld(const real3& longLatx) override 11 { 12 return _spr.longlatAltToWorld(longLatx); 13 } 14 15 /** 16 * 世界坐标转化为经纬度 17 */ 18 virtual real3 worldToLongLat(const real3& world) override 19 { 20 return _spr.worldToLonglatAlt(world); 21 } 22 23 /** 24 * 根据经纬度与级别返回瓦片Key 25 */ 26 virtual int3 getKey(unsigned level, real rLong,real rLat) override 27 { 28 unsigned xTiles = 2<<level; 29 unsigned yTiles = 1<<level; 30 double xWidth = TWO_PI / xTiles; 31 double yHeight = PI / yTiles; 32 double xCoord = (rLong + PI) / xWidth; 33 if (xCoord >= xTiles) 34 { 35 xCoord = xTiles - 1; 36 } 37 38 double yCoord = (HALF_PI - rLat) / yHeight; 39 if (yCoord >= yTiles) 40 { 41 yCoord = yTiles - 1; 42 } 43 44 return int3(int(xCoord),int(yCoord),level); 45 }
3.3 四叉树
结构定义
1 class CELLQuadTree :public CELLObject 2 { 3 public: 4 enum ChildId 5 { 6 CHILD_LT, 7 CHILD_RT, 8 CHILD_LB, 9 CHILD_RB, 10 }; 11 enum 12 { 13 FLAG_HAS_IMAGE = 1<<0, 14 FLAG_HAS_DEM = 1<<1, 15 FLAG_HAS_CULL = 1<<2, 16 FLAG_RENDER = 1<<3, 17 }; 18 public: 19 typedef std::vector<CELLQuadTree*> ArrayNode; 20 typedef CELLTerrainInterface TerrainApi; 21 using NodePtr = std::shared_ptr<CELLQuadTree>; 22 public: 23 TerrainApi* _terrain; 24 /// 数据标志 25 uint _flag; 26 /// 对应瓦片id 27 TileId _tileId; 28 real2 _vStart; 29 real2 _vEnd; 30 matrix4r _local = matrix4r(1); 31 32 float2 _uvStart; 33 float2 _uvEnd; 34 /// 对应瓦片的范围(世界坐标) 35 aabb3dr _aabb; 36 /// 位置 37 ChildId _cornerId; 38 /// 当前瓦片的父节点 39 CELLQuadTree* _parent; 40 /// 瓦片的孩子节点 41 NodePtr _childs[4]; 42 /// 纹理id 43 uint _textureId; 44 /// dem数据 45 DemData _demData; 46 VertexBufferId _vbo; 47 IndexBufferId _ibo; 48 };
3.3.1 对象管理
GIS 系统中会频繁大量加载卸载地图数据(动态加卸载),时间长了会产生大量的内存碎片,影像加载绘制效率,为了提升性能
系统使用内存池以及对象池来优化瓦片数据。
引入基类定义,方便扩展新的特性
class CELLPool :public CELLObject { public: }; using PoolPtr = std::shared_ptr<CELLPool>;
内存池定义如下:
使用模板实现,采用单例设计模式对外提供内存管理能力
template<size_t blackSize> class CELLPoolMemory :public CELLPool { public: struct Data { char _data[blackSize]; }; using ObjectList = std::list<void*>; protected: ObjectList _objests; public: ~CELLPoolMemory() { destroy(); } void init(size_t nCnt) { for (size_t i = 0; i < nCnt; i++) { auto obj = new Data(); _objests.push_back(obj); } } void* alloc() { if (_objests.empty()) { return new Data(); } else { void* ptr = _objests.back(); _objests.pop_back(); return ptr; } } /// <summary> /// 回收函数 /// </summary> void free(void* obj) { _objests.push_back(obj); } void destroy() { for (auto var : _objests) { delete var; } _objests.clear(); } public: static CELLPoolMemory<blackSize>& instance() { static CELLPoolMemory<blackSize> sInstance; return sInstance; } };
对象池定义如下:
使用模板实现,采用单例设计模式对外提供内存管理能力
template<class T> class CELLPoolObject :public CELLPool { public: using TPtr = T*; using ObjectList = std::list<TPtr>; using SPtr = std::shared_ptr<T>; protected: ObjectList _objests; public: /// <summary> /// 初始化 /// </summary> template<class ...Args> void init(size_t nCnt,Args ...args) { for (size_t i = 0; i < nCnt; i++) { TPtr ptr = new T(args...); _objests.push_back(ptr); } } /// <summary> /// 分配函数 /// </summary> template<class ...Args> SPtr alloc(Args ...args) { if (_objests.empty()) { return SPtr(new T(args...),deleteObject); } else { TPtr ptr = _objests.back(); _objests.pop_back(); return SPtr(ptr,deleteObject); } } /// <summary> /// 回收函数 /// </summary> void free(TPtr obj) { _objests.push_back(obj); } void destroy() { _objests.clear(); } public: static inline void deleteObject(T* obj) { instance().free(obj); } static CELLPoolObject<T>& instance() { static CELLPoolObject<T> sInstance; return sInstance; } };
3.3.2 网络数据生成
主要是生成可以绘制的三角网络数据,为了统一接口,实现如下;
struct V3U3N4 { float x, y, z; float h; float u, v; }; template<ushort row,ushort col> class CELLDemMesh :public CELLObject { protected: V3U3N4 _data[row * col]; ushort3 _faces[(row -1) * (col - 1) * 2]; int _col = 0; int _row = 0; aabb3dr _aabb; public: CELLDemMesh() { _row = row; _col = col; } void redefine(int c,int r) { _col = c; _row = r; } int getRow() const { return _row; } int getCol() const { return _col; } V3U3N4* vertData() { return _data; } const V3U3N4* vertData() const { return _data; } int vertCount() const { return _row * _col; } int vertByte() const { return vertCount() * sizeof(V3U3N4); } ushort3* faceData() { return _faces; } const ushort3* faceData() const { return _faces; } int faceCount() const { return (_row -1) * (_col - 1) * 2; } int faceByte() const { return faceCount() * sizeof(ushort3); } void fillFace() { uint fSize = (_row -1) * (_col - 1) * 2; uint index = 0; for (ushort r = 0; r < _row - 1; ++r) { for (ushort c = 0; c < _col - 1; ++c) { _faces[index + 0][0] = (_col * r) + c; _faces[index + 0][1] = (_col * r) + c + 1; _faces[index + 0][2] = (_col * (r + 1)) + c; _faces[index + 1][0] = (_col * r) + c + 1; _faces[index + 1][1] = _col * (r + 1) + c + 1; _faces[index + 1][2] = _col * (r + 1) + c; index += 2; } } } void fillVertex(const real2& vStart,const real2& vEnd,bool bInitY,CELLSpRef* spRef) { real2 vSize = vEnd - vStart; real2 vGrid = real2(vSize.x/(_col -1),vSize.y/(_row - 1)); _aabb.setNull(); real3 tmp[row * col]; if (bInitY) { for (ushort r = 0; r < _row; ++r) { for (ushort c = 0; c < _col; ++c) { int idx = r * _col + c; real3 vWorld = spRef->longLatToWorld(real3(vStart.x + c * vGrid.x,vStart.y + r * vGrid.y,0)); tmp[idx] = vWorld; /// _data[idx].x = vWorld.x; /// _data[idx].y = vWorld.y; /// _data[idx].z = vWorld.z; _aabb.merge(vWorld); } } } else { for (ushort r = 0; r < _row; ++r) { for (ushort c = 0; c < _col; ++c) { int idx = r * _col + c; real3 vWorld = spRef->longLatToWorld(real3(vStart.x + c * vGrid.x,vStart.y + r * vGrid.y,_data[idx].h)); tmp[idx] = vWorld; /// _data[idx].x = vWorld.x; /// _data[idx].y = vWorld.y; /// _data[idx].z = vWorld.z; _aabb.merge(vWorld); } } } real3 vCenter = _aabb.getCenter(); for (size_t i = 0; i < row * col; i++) { real3 vLocal = tmp[i] - vCenter; _data[i].x = vLocal.x; _data[i].y = vLocal.y; _data[i].z = vLocal.z; } } void fillUV(const real2& vStart,const real2& vEnd,int layer) { real2 uvSize = vEnd - vStart; real2 uvStep = real2(uvSize.x/(_col -1),uvSize.y/(_row - 1)); for (ushort r = 0; r < _row; ++r) { for (ushort c = 0; c < _col; ++c) { int idx = r * _col + c; _data[idx].u = (vStart.x + real(c) * uvStep.x); _data[idx].v = (vStart.y + real(r) * uvStep.y); } } } void fillHeight(CELLFiledHeight* pHeight) { float* pData = pHeight->data(); for (size_t i = 0 ;i < _row * _col ; ++ i) { _data[i].h = pData[i]; } } aabb3dr getAabb() const { return _aabb; } /// <summary> /// 瓦片与射线相交测试 /// </summary> bool intersect(const CELL::Ray& ray,const matrix4r& mat,real& dist) { int faceCnt = faceCount(); real time = FLT_MAX; bool bPick = false; auto result = ray.intersects(_aabb); if (!result.first) return false; for (int i = 0; i < faceCnt; i++) { int i1 = _faces[i].x; int i2 = _faces[i].y; int i3 = _faces[i].z; real3 v1 = mat * real3(_data[i1].x,_data[i1].y,_data[i1].z); real3 v2 = mat * real3(_data[i2].x,_data[i2].y,_data[i2].z); real3 v3 = mat * real3(_data[i3].x,_data[i3].y,_data[i3].z); real cur = FLT_MAX; real u = 0; real v = 0; bool res = CELL::intersectTriangle<real>(ray.getOrigin(),ray.getDirection(),v1,v2,v3,&cur,&u,&v); if (res) { time = CELL::tmin(time,cur); bPick = true; } } if (bPick) { dist = time; } return bPick; } }; using DemData = CELLDemMesh<DEM_W,DEM_H>;
3.3.3 裂分算法实现(核心部分实现)
CELLCamera& camera = context._camera; real3 vWCenter = _aabb.getCenter(); real3 vWSize = _aabb.getSize(); real fSize = CELL::length(vWSize) * 0.5; real dis = CELL::length(vWCenter - camera._eye); if (context._frustum.cubeInFrustum( _aabb._minimum.x ,_aabb._maximum.x ,_aabb._minimum.y ,_aabb._maximum.y ,_aabb._minimum.z ,_aabb._maximum.z)) { _flag &= ~FLAG_HAS_CULL; } else { _flag |= FLAG_HAS_CULL; } if (dis/fSize < 3 && hasNoFlag(FLAG_HAS_CULL)) { if(!hasChild() && hasImage()) { real2 vLlCenter = lonLatCenter(); real2 vLLHalf = lonLatRange() * 0.5; real3 vCenter = real3(vLlCenter.x, 0, vLlCenter.y); real3 vSize = real3(vLLHalf.x, 0, vLLHalf.y); if (_terrain->getTaskCount() > 20) { return ; } _childs[CHILD_LT] = NodePtr(new CELLQuadTree( _terrain , this ,real2(vCenter.x - vSize.x,vCenter.z) ,real2(vCenter.x,vCenter.z + vSize.z) ,(int)_tileId._lev + 1 ,CHILD_LT )); _childs[CHILD_RT] = NodePtr(new CELLQuadTree( _terrain ,this , real2(vCenter.x, vCenter.z) , real2(vCenter.x + vSize.x, vCenter.z + vSize.z) , (int)_tileId._lev + 1 , CHILD_RT )); _childs[CHILD_LB] = NodePtr(new CELLQuadTree( _terrain , this , real2(vCenter.x - vSize.x, vCenter.z - vSize.z) , real2(vCenter.x, vCenter.z) , (int)_tileId._lev + 1 , CHILD_LB )); _childs[CHILD_RB] = NodePtr(new CELLQuadTree( _terrain , this , real2(vCenter.x, vCenter.z - vSize.z) , real2(vCenter.x + vSize.x, vCenter.z) , (int)_tileId._lev + 1 , CHILD_RB )); } else { for (int i = 0; i < 4; ++i) { if (_childs[i] && hasNoFlag(FLAG_HAS_CULL)) { _childs[i]->update(context); } else { _flag &= FLAG_RENDER; } } } } else { for (int i = 0 ;i < 4 ; ++ i) { _childs[i] = nullptr; } }
3.4 图层管理
3.5 文件格式管理
为了统一接口,提炼一个通用的基类如下代码所示:
class ExtDesc { public: std::string _ext; std::string _version; std::string _desc; }; using ExtDescs = std::vector<ExtDesc>; class CELLFormat :public CELLObject { public: friend class CELLFormatMgr; protected: ExtDescs _descs; public: /// <summary> /// 判断是否支持对应扩展名的文件 /// </summary> bool support(const std::string& ext,const std::string& version = "1.0.0.0") { for (auto& var : _descs) { if (var._ext != ext ) continue; if (var._version != version ) continue; return true; } return false; } /// <summary> /// 判断是否有子节点 /// </summary> virtual ObjectPtr read(const char* fileName) = 0; }; using FormatPtr = std::shared_ptr<CELLFormat>;
3.5.1 dem文件读取实现
图1为5米格网的DEM,图2为ALOS 12.5米分辨率的DEM,图3为ASTER GDEM V3 30米分辨率的DEM,图4为SRTM3 90米分辨率的DEM。
大多数项目上使用的是30米的数据源,可以从网上上下载,高程数据可以理解成一个二维数据,每一个元素存储的是高度信息(海拔)
GIS中加载考虑到速度,也会把瓦片切分成标准的瓦片,本节是读取cesium高程数据代码:
3.5.2 terrain文件读取实现
FILE* pFile = fopen(fileName,"rb"); if (pFile == nullptr) return nullptr; uint16_t data[65 * 65] = {0}; CELL::CELLFieldHeight* pHeight = dynamic_cast<CELL::CELLFieldHeight*>(user); if (pHeight == nullptr) { pHeight = new CELL::FEFieldHeight(); } pHeight->create(65,65,0); fread(data,1,sizeof(data),pFile); fclose(pFile); float* pData = pHeight->data(); for (size_t i = 0 ;i < 65 * 65;++i) { pData[i] = float(data[i]) * 0.2f; } for (int j = 0, k = 65; j < 65 / 2; j++) { for (int i = 0; i < 65; i++) { auto temp = pData[j * k + i]; pData[j * k + i] = pData[(65 - j - 1) * k + i]; pData[(65 - j - 1) * k + i] = temp; } } return pHeight;
3.6 任务系统实现
采用生产者与消费者模式:
一对一模式:
多对多模式:
系统中瓦片加载的典型应用如下图描述,这里注意,创建纹理必须在主线程中完成。
主要功能,支持多线程中执行任务
支持取消任务
支持任务执行失败通知
支持任务执行成功通知
class CELLTaskObserver { public: /// <summary> /// 任务取消通知 /// </summary> virtual void onTaskCancel(CELLTask* task) = 0; /// <summary> /// 任务完成通知 /// </summary> virtual void onTaskExe(CELLTask* task) = 0; /// <summary> /// 任务完成通知 /// </summary> virtual void onTaskFinish(CELLTask* task) = 0; }; class CELLTaskSystem { public: class TaskThread :public CELLThread { public: bool _exitFlag; CELLTaskSystem* _system; public: TaskThread(CELLTaskSystem* pSystem) { _system = pSystem; _exitFlag = true; } public: virtual void join() { _exitFlag = true; CELLThread::join(); } virtual bool onCreate() { _exitFlag = false; return false; } virtual bool onRun() { while (!_exitFlag) { _system->run(); } return false; } virtual bool onDestroy() { return false; } }; typedef std::vector<CELLThread*> ArrayThread; typedef std::list<TaskPtr> ArrayTask; public: CELLTaskObserver* _observer; ArrayThread _threads; ArrayTask _tasks; CELLSemaphore _semphore; mutable CELLMutex _mutex; public: CELLTaskSystem() :_observer(0) {} /// <summary> /// /// </summary> virtual ~CELLTaskSystem() {} /// <summary> /// 设置观察者指针 /// </summary> virtual void setObserver(CELLTaskObserver* observer) { _observer = observer; } /// <summary> /// 启动任务处理 /// </summary> virtual void start(int threadNum = 4) { destroy(); for (int i = 0 ;i < threadNum ; ++ i) { TaskThread* pThread = new TaskThread(this); pThread->start(); _threads.push_back(pThread); } } /// <summary> /// 销毁 /// </summary> virtual void destroy() { for (size_t i = 0 ;i < _threads.size() ; ++ i) { _threads[i]->join(); delete _threads[i]; } _threads.clear(); } /// <summary> /// 添加任务接口 /// </summary> virtual void addTask(CELLTask* task) { { CELLMutex::ScopeLock lk(_mutex); _tasks.push_back(task->toPtr<CELLTask>()); } _semphore.set(1); } /// <summary> /// 获取任务队列中的任务个数 /// </summary> virtual size_t getTaskCount() const { CELLMutex::ScopeLock lk(_mutex); return _tasks.size(); } public: virtual void run() { if(!_semphore.wait()) { return; } TaskPtr pTask = nullptr; { CELLMutex::ScopeLock lk(_mutex); if (_tasks.empty()) { return; } /// 1. 取数据 pTask = _tasks.front(); _tasks.pop_front(); } /// 2. 执行过程 /// 3. 通知过程 if (_observer && pTask) { _observer->onTaskExe(pTask.get()); _observer->onTaskFinish(pTask.get()); } } };
文章来源地址https://www.toymoban.com/news/detail-710836.html
到了这里,关于自主三维GIS引擎笔记-实现三维球045的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!