隨著新能源技術的迅速發展,低空經濟逐漸成為新的戰略性新興產業。然而,與傳統的地面活動不同,低空活動具有立體性、地區性和融合性等特點,這些特點為安全引導低空活動的順利開展帶來了系列亟待解決的技術性問題。GanosBase GeoSOT地理網格引擎提供了基於網格的路徑規劃能力,能夠利用數字高程模型(DEM)、數字表面模型(DSM)、傾斜攝影等資料構建複雜環境中的無人機路徑規劃應用。
關於GanosBase地理網格模型
地理網格是一種用於再現地球表面的多邊形網格資料格集合,能夠有效表示地物在地理空間中的位置資訊,並融合其他各類時空資料。地理格線運算通常採用由粗到細的逐級分割方式,對地球表面進行處理。通過將地球的曲面用一定大小的多邊形網格進行近似類比,實現地理空間定位與地理特徵描述的有機結合,同時將誤差範圍控制在網格單元的可接受範圍內。每個網格單元都會進行編碼,網格與編碼是一一對應的。三維地理網格不只考慮經緯度,還把高度維納入剖分和編碼範圍。
GanosBase地理網格引擎目前涵蓋GeoSOT和H3兩種地理網格。
GeoSOT是中國提出的一套地球空間剖分理論,並在此基礎上發展出的一種離散化、多尺度地區位置標識體系。
H3是Uber研發的一種覆蓋全球表面的二維地理網格,採用的基本網格是正六邊形。關於H3網格的最佳實務可參考H3地理網格能力解析與最佳實務。
GanosBase支援退化格線運算(如下圖),充分利用網格的層級關係,通過更精簡的網格組合對空間範圍進行有效表達。此外,GanosBase自研的地理網格索引,可用於高效查詢網格編碼並加速彙總計算。

業務背景
隨著新能源技術的迅猛發展,低空經濟已經逐步成為新的戰略性新興產業。低空經濟是一種綜合性經濟形態,依託各種有人駕駛和無人駕駛航空器的低空飛行活動,推動相關領域的融合發展。其應用情境豐富,不僅涵蓋了傳統通用航空業態,還融合了以無人機為支撐的低空生產服務方式,廣泛應用於工業、農業和服務業等多個領域。低空經濟在構建現代產業體系方面具有重要作用,且發展空間極為廣闊。
不同於傳統的地表活動,低空活動具備立體性、地區性和融合性等特徵。這些特徵為安全引導低空活動的順利開展帶來了一系列亟待解決的技術問題。
立體性:主要體現為“活動空間上的立體性”。低空空域是指其垂直範圍原則為真實高度1000米以下。根據不同地區的特點和實際需求,具體劃定的高度範圍構成了空域的活動空間。這一空域的劃分使經濟活動得以從地面向空中延伸,實現從“平面經濟”向“立體經濟”的轉變,體現了一種三維空間的立體經濟形態。
地區性:主要體現為“作用範圍上的地區性”。與航空運輸經濟、高鐵經濟等大規模、大範圍、一體化特徵的經濟形式不同,低空經濟基於小飛機、小航線和小企業,具有小規模、小範圍及分散性特點,且顯著體現出地區性和地區性特徵。
融合性:主要體現為“運行模式上的融合性”。與航空運輸及鐵路、公路、水運等交通運輸經濟相比,低空經濟的主要作用不僅限於交通運輸,更側重於行業服務。其體現為“行業+航空”的模式,為相關行業提供空中解決方案或輔助手段,以提升工作效率、降低成本、增強獲得感等。
通過分析低空活動的特性可以發現,低空地區並不存在路網。在三維空間中,如何確保低空裝置的安全通行與到達,以及如何合理劃分低空地區的活躍周期與飛行器的活動規劃,都是低空經濟順利實施並保障地區活躍性與安全的重要工作。
功能介紹
GanosBase地理網格引擎提供了基於網格的路徑規劃能力,能夠利用數字高程模型(DEM)、數字表面模型(DSM)、傾斜攝影等資料構建複雜環境中的無人機路徑規劃應用情境。相關函數簡介如下:
用於給障礙物設定通行開銷。障礙物可以是實體,例如地面、建築物,也可以是空間範圍,如雷達掃描範圍或風場空間。在使用該函數之前,必須首先將障礙物的空間範圍轉換為網格數組,然後進行通行開銷的設定。
無人機路徑規劃的最核心函數,根據起始點、終止點以及通行的開銷資料,計算出最優的三維網格路徑。支援不同的路徑演算法,如A*和Dijkstra,不同的移動方式比如是否允許斜向飛行,以及不同的距離計算方法比如曼哈頓和歐幾裡得。
根據地形高程資料的解析度確定最高的網格剖分層級,路徑計算時格線運算的層級應低於或等於此層級。
合并所有的障礙物網格開銷數組,支援退化網格合并,重疊的網格採用最大的開銷並只保留一個,作為最終規劃路線的輸入。
技術優勢
基於GanosBase GeoSOT地理網格引擎開展無人機路徑規劃能力的支撐,具備以下技術特點:
統一的標準資料模型,所有輸入資料均在GanosBase內部採用標準資料模型進行表達(包括柵格、表面網格等)。
所有相關的資料處理均可通過GanosBase內建函式完成,同時還提供了完善的路徑規劃輔助函數。
無人機路徑規劃可以採用退化網格技術,以降低儲存開銷並提升計算速度。
路徑規劃提供多種參數選項,包括規划算法、移動方式和距離計算方法,以滿足不同的需求。
最佳實務
本部分以某園區的傾斜攝影資料為例,示範三維路徑規劃在實際情境中的應用。
建議配置
為了得到良好的體驗,PolarDB叢集建議使用以下配置:
資料庫引擎:PostgreSQL 14
核心版本:14.12.23.1
CPU:>= 4 核
記憶體:>= 16 GB
磁碟:>= 100 GB
GanosBase版本:>=6.7
在匯入及計算OSGB資料的過程中,需要佔用一定量的記憶體,建議記憶體容量大於16 GB。
測試資料

資料入庫與處理
安裝網格路徑規劃功能所需外掛程式。
說明如執行以下語句進行外掛程式安裝異常請聯絡我們處理。
CREATE EXTENSION IF NOT EXISTS ganos_geomgrid CASCADE; CREATE EXTENSION IF NOT EXISTS ganos_utility CASCADE;測試資料存放區於OSS中,使用ST_ImportOSGB函數將其匯入,詳細參數介紹可參考ST_ImportOSGB。
SELECT ST_ImportOSGB( 'test_oblique', 'OSS://<access_id>:<secrect_key>@<Endpoint>/<bucket>/path_to/file', '{"parallel":16,"project":"building"}');將某一層級的瓦塊整體轉換為單一GLB格式,並存入暫存資料表temp_glb中,採用SfMesh類型作為中間類型。
SELECT ST_ImportGLB( 'temp_glb', ST_AsGLB(ST_Combine(tile)), 'temp_glb_1', '{"ignore_texture":true,"ignore_normal":true}' ) FROM test_oblique_tile WHERE lod = 19 AND project_name = 'building';說明調用ST_ImportGLB函數時,忽略無用的紋理( ignore_texture )和法線( ignore_normal )。
此處選用
lod = 19的瓦塊作為採樣瓦塊。您可以自行選擇適當層級的瓦塊。請注意,更高LOD層級的瓦塊雖然會顯著延長採樣時間,但其精度也相應提高。測試傾斜資料為常見的傾斜資料命名,例如
Tile_+006_+004_L14_0.osgb,可以從檔案名稱中分析出其LOD層級。對於命名沒有規律的傾斜資料,因無法擷取其LOD值,可以將
WHERE lod = 19替換為WHERE children IS NULL,即選用最高精度的分葉節點瓦塊作為採樣瓦塊。
更新臨時資料。將其投影至
EPSG:4490座標系下:UPDATE temp_glb SET gltf_data = ST_Transform( ST_Flatten( ST_SetSrid( ST_Translate( ST_YupToZup(gltf_data), x_off, y_off,z_off), srid), FALSE), 4490) FROM (SELECT srid, ST_X(anchor) x_off, ST_Y(anchor) y_off, ST_Z(anchor) z_off FROM test_oblique WHERE project_name = 'building') metadata WHERE gltf_id = 'temp_glb_1';上述語句執行了如下操作:
OSGB入庫後轉換為GLB時是Y軸向上。為正常處理其座標,需要調用ST_YupToZup函數將其轉換為Z軸向上。
傾斜資料往往以相對座標記錄點,因此需要調用ST_Translate函數將每個點都以錨點(anchor)做位移,使每個點的座標都為絕對座標,並通過ST_SetSrid函數設定正確的SRID。
說明該操作要求原始傾斜資料具有完整的中繼資料描述檔案(metadata.xml),否則無法正確地擷取錨點和座標系。
對於包含旋轉矩陣的SfMesh對象,需預先進行壓平處理(ST_Flatten),以將旋轉矩陣應用於實際座標,便於進行座標投影。
由於網格自身的座標系規定為CGC 2000,因此需要利用ST_Transform函數將每個點投影至EPSG:4490座標系。
將中間資料轉換為幾何網格(GeomGrid)。
-- 建立網格表 CREATE TABLE IF NOT EXISTS building_grid ( id SERIAL, grid GEOMGRID, grid_type TEXT ); -- 產生GeomGrid並插入網格表 INSERT INTO building_grid (grid, grid_type) SELECT grid, 'border' FROM (SELECT unnest(ST_As3DGrid(gltf_data, 24, TRUE)) grid FROM temp_glb WHERE gltf_id = 'temp_glb_1' ) tmp; -- 移除暫存資料表 DROP TABLE temp_glb;其中,ST_As3DGrid函數能夠根據實際需求指定地理網格的精度層級。精度越高,所得到的網格越為細緻,然而,採樣所需的時間也將顯著增加。不同層級下每個網格的大致尺度可以參考下表:
層級
大致尺度
層級
大致尺度
層級
大致尺度
0
全球
11
29.6 km
22
15.5 m
1
1/4 地球
12
14.8 km
23
7.7 m
2
-
13
7.4 km
24
3.9 m
3
-
14
3.7 km
25
1.9 m
4
-
15
1.8 km
26
1.0 m
5
-
16
989.5 m
27
0.5 m
6
890.5 km
17
494.7 m
28
24.2 cm
7
445.3 km
18
247.4 m
29
12.0 cm
8
222.6 km
19
123.7 m
30
6.0 cm
9
111.3 km
20
61.8 m
31
3.0 cm
10
59.2 km
21
30.9 m
32
1.5 cm
網格展示
產生的網格可以匯出為GLB格式,以便於瀏覽大致形狀。
SELECT ST_AsGLB( ST_ZupToYup( ST_3DRemoveDuplicateVertex( ST_MergeGeomByMaterial( ST_Triangulate( ST_Translate( ST_Transform( ST_Flatten( ST_Collect( ST_AsMeshgeom(array (SELECT grid FROM building_grid WHERE grid_type = 'border')):: sfmesh[]), TRUE ), srid ), - ST_X(anchor), - ST_Y(anchor), - ST_Z(anchor) ) ) ), 0.1 ) ) ) FROM test_oblique WHERE project_name = 'building';上述語句執行了如下操作:
通過ST_AsMeshGeom函數將網格數群組轉換為MeshGeom數組。
將MeshGeom數群組類型轉換為SFMesh數組,並彙總(ST_Collect)成獨立的SfMesh對象。
將彙總後的對象壓平(ST_Flatten),並重新投影(ST_Transform)回原始座標系。
將彙總後的對象整體位移(ST_Translate),使全部座標點都變回相對於原點的相對座標。
將彙總後的對象三角化(ST_Triangulate),方便接下來的處理步驟。
聯合調用按材質合并幾何函數(ST_MergeGeomByMaterial)與按閾值移除點函數(ST_3DRemoveDuplicateVertex)可以減少資料量,降低渲染壓力。
調用ST_YupToZup函數將左手座標系下的資料轉換到GLTF所使用的右手座標系,最終調用ST_AsGLB函數匯出GLB。
不同層級的效果對比可參見下表(以25級和24級為例):
層級
效果展示
未經處理資料

25級

24級

網格對象也可以通過以下語句產生適合Cesium讀取的資料:
-- 建立函數方便後期調用 CREATE OR REPLACE FUNCTION GridToJson(geomgrid[]) RETURNS json AS $$ SELECT to_json( array_agg( json_build_object('center', array_to_json(array[ST_X(center), ST_Y(center), ST_Z(center)]), 'size', array_to_json(array[ST_DistanceSpheroid(min_point, ST_Point(ST_X(max_point), ST_Y(min_point), 4490)), ST_DistanceSpheroid(min_point, ST_Point(ST_X(min_point), ST_Y(max_point), 4490)), z])))) FROM (SELECT ST_Transform(ST_PointZ(ST_X(st_centroid(geom)), ST_Y(st_centroid(geom)), (ST_ZMax(geom) + ST_ZMin(geom)) / 2, 4490), 4479) center, ST_SetSrid(BOX[0]::geometry, 4490) min_point, ST_SetSrid(BOX[1]::geometry, 4490) max_point, ST_ZMax(geom) - ST_ZMin(geom) z FROM (SELECT ST_ASBox(grid) BOX, ST_AsGeometry(grid) geom FROM (SELECT unnest($1) grid) a)b)c $$ LANGUAGE 'sql'; -- 調用GridToJson函數產生結果 SELECT GridToJson(array (SELECT grid FROM building_grid WHERE grid_type = 'border')) ;說明以上語句建立的GridToJson函數將網格對象轉換為包含每個網格元素的中心位置和大小的JSON格式。其中,調用ST_Transform函數將網格對象的空間參考系統由SRID 4490轉換為SRID 4479,導致最終擷取的JSON中,
center欄位的座標值為4479。您可根據實際情況進行調整。返回結果為JSON格式的數組,如下:
[ { "center": [-1583397.2647956165,5313841.088458569,3142388.7651142543], "size": [3.3600142470488144,3.848872238575258,3.3680849811062217] }, ... ]其中:
center:每一個網格中心點投影至EPSG:4479座標系下的座標。
size:每個網格在緯度方向/經度方向/高度方向的大致尺寸。
在Cesium中,可以通過如下方式載入上述資料:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <script src="https://cesium.com/downloads/cesiumjs/releases/1.89/Build/Cesium/Cesium.js"></script> <link href="https://cesium.com/downloads/cesiumjs/releases/1.89/Build/Cesium/Widgets/widgets.css" rel="stylesheet"> <style> html, body, #cesium_container { width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; } </style> </head> <body> <div id="cesium_container"></div> <script> Cesium.Ion.defaultAccessToken = YOUR_CESIUM_TOKEN const viewer = new Cesium.Viewer('cesium_container', ['timeline', 'animation', 'infoBox', 'navigationHelpButton'].reduce((opts, opt) => (opts[opt] = false) || opts, {})) // 指定需要顯示的網格 const grids = [{ "center": [-1583315.44750805, 5313895.98214751, 3142303.525767127], "size":... const add_grids = (grids, material) => grids.forEach(({ center, size }) => viewer.entities.add({ position: new Cesium.Cartesian3(...center), box: {dimensions: new Cesium.Cartesian3(...size), material, outline: true, outlineColor: Cesium.Color.BLACK}})) // 使用半透明的紅色展示 add_grids(grids, Cesium.Color.RED.withAlpha(0.2)) // 跳轉到網格位置 viewer.zoomTo(viewer.entities) </script> </body> </html>將24級網格與傾斜資料疊加顯示,疊加傾斜資料代碼請參考附錄效果如下:

網格路徑規劃
構建網格後,可以在上述情境中以某兩點作為起止點,使用ST_3DGridPath函數規劃基於網格的三維路徑:
-- 定義起點/終點/可搜尋範圍
WITH vals (start_p, end_p, box_range) AS (
VALUES
(
ST_PointZ(106.59182291666669, 29.70644097140958, 355.322855208642, 4490),
ST_PointZ(106.59244791666666, 29.707135415854022, 348.58669235736755, 4490),
'BOX3D(106.591788194444 29.7062326380763 341.85053662386713, 106.592899305556 29.7071701380762 362.0590251699541)' :: box3d
)),
border AS (
SELECT DISTINCT grid, grid_type
FROM building_grid, vals
WHERE
-- 僅計算範圍內的網格
-- 所有參與計算的幾何類型均要求空間參考為EPSG:4490
ST_3DIntersects(ST_SetSrid(box_range :: meshgeom, 4490), grid)
OR ST_3DContains(ST_SetSrid(box_range :: meshgeom, 4490), grid)
)
SELECT ST_3DGridPath(start_p, end_p, box_range, array[ST_SetCost( array (SELECT grid FROM border WHERE grid_type = 'border' ), -1)], '{"algorithm":"astar","movement":"cross","distance":"euclidean"}') result
FROM vals;其中:
ST_SetCost函數為網格設定同行成本。
不可通行區固定為-1。
若無其他設定,任意非不可通行區的通行成本固定為1。
使用上文建立的GridToJson函數,可以在Cesium中瀏覽效果:



若將移動方式修改為strict_octothorpe,則其效果為:



多成本區網格路徑規劃
在可通列區域為不同地區設定不同的通行成本後,演算法將選擇綜合成本最小的路徑。我們在前述情境中添加了一個高成本通列區域,以示範多成本網格路徑規劃的應用。
在building_grid表中插入代表高成本通行區的網格:
INSERT INTO building_grid (grid, grid_type) SELECT grid, 'risk_zone' FROM (SELECT unnest(ST_As3DGrid(ST_SetSrid('BOX3D(106.59192708333332 29.706614582520686 341.8505366235368, 106.59220486111111 29.706892360298472 365.4271128197744)' :: box3d :: meshgeom, 4490), 24)) grid) tmp;該地區形如(橘色部分):
說明與非通行網格重合的高成本通行區網格也將視為非通行網格。
執行路徑規劃。
WITH vals (start_p, end_p, box_range) AS ( VALUES ( ST_PointZ(106.59182291666669, 29.70644097140958, 355.322855208642, 4490), ST_PointZ(106.59244791666666, 29.707135415854022, 348.58669235736755, 4490), 'BOX3D(106.591788194444 29.7062326380763 341.85053662386713, 106.592899305556 29.7071701380762 362.0590251699541)' :: box3d )), border AS ( SELECT DISTINCT grid, grid_type FROM building_grid, vals WHERE ST_3DIntersects(ST_SetSrid(box_range :: meshgeom, 4490), grid) OR ST_3DContains(ST_SetSrid(box_range :: meshgeom, 4490), grid) ) -- 設定 risk_zone 的通行成本為2 SELECT ST_3DGridPath(start_p, end_p, box_range, array[ST_SetCost( array (SELECT grid FROM border WHERE grid_type = 'border' ), -1), ST_SetCost( array (SELECT grid FROM border WHERE grid_type = 'risk_zone' ), 2)], '{"algorithm":"astar","movement":"cross","distance":"euclidean"}') result FROM vals;此時效果為:



對比沒有高成本通列區域的路徑(紫色路徑):

總結
利用GanosBase地理網格引擎的相關方法,通過少量代碼實現無人機路徑規劃。GanosBase作為全球首個專業級空間資料庫,已經將狹義的空間資料拓展至“空天地、室內外、地上下、動靜態”等全空間範疇,從資料庫系統最底層為物理世界數字化提供時空處理架構,未來GanosBase還將提供更多高效的庫內空間分析與可視化能力,推動各行業的空間資訊應用真正走向“視算一體”。
附錄
使用Node.js實現簡易的後端服務,代理3DTiles請求,並以Cesium作為前端架構展示結果。
在自訂目錄下編寫依賴檔案
package.json:{ "dependencies": { "koa": "^2.14.2", "koa-router": "^12.0.0", "koa-send": "^5.0.1", "pg": "^8.10.0" } }在該目錄執行
npm install安裝依賴庫。在同一目錄下建立Node.js指令檔index.js:const Koa = require('koa'); const Send = require('koa-send'); const Router = require('koa-router'); const router = new Router() const { Pool } = require('pg'); const pool = new Pool({ user: <YOUR_USER>, host: <YOUR_HOST>, database: <YOUR_DB_NAME>, port: <YOUR_PORT> }); const TABLE_NAME = 'test_osgb' const PROJECT_NAME = 'prj1' router.get('/', (_) => Send(_, '/index.html')) /* get metadata of current project */ router.get('/project', async (ctx) => { const sql = `SELECT (REGEXP_MATCHES(ST_ASTEXT(ST_TRANSFORM(ANCHOR,4326)), 'POINT Z \\((.*?)\\)'))[1] _ANCHOR, PROJECT_ID FROM ${TABLE_NAME} WHERE PROJECT_NAME='${PROJECT_NAME}';` const { rows: [{ _anchor, project_id }] } = await pool.query(sql) const anchor = _anchor.split(' ').map(x => parseFloat(x)) const url = `/tileset/${project_id}/${project_id}` ctx.body = { url, anchor } }) router.get('/tileset/:project_id/:uid', async (ctx) => { const { params: { project_id, uid } } = ctx const sql = `SELECT TILESET FROM ${TABLE_NAME}_TILESET WHERE PROJECT_ID=$1::UUID AND UID=$2::UUID;` const { rows: [{ tileset }] } = await pool.query(sql, [project_id, uid]) ctx.body = tileset }) router.get('/b3dm/:project_id/:uid', async (ctx) => { const { params: { project_id, uid } } = ctx const sql = `SELECT ST_ASB3DM(TILE) TILE FROM ${TABLE_NAME}_TILE WHERE PROJECT_ID=$1::UUID AND UID=$2::UUID;` const { rows: [{ tile }] } = await pool.query(sql, [project_id, uid]) ctx.body = tile }) new Koa().use(router.routes()).listen(5500, '0.0.0.0');在同一目錄下建立HTML檔案
index.html。<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <script src="https://cesium.com/downloads/cesiumjs/releases/1.89/Build/Cesium/Cesium.js"></script> <link href="https://cesium.com/downloads/cesiumjs/releases/1.89/Build/Cesium/Widgets/widgets.css" rel="stylesheet"> <style> html, body, #cesium_container { width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; } </style> </head> <body> <div id="cesium_container"></div> <script> Cesium.Ion.defaultAccessToken = <SET_YOUR_OWN_TOKEN_HERE> const disable_opt = ['timeline', 'animation', 'infoBox', 'navigationHelpButton'] .reduce((opts, opt) => (opts[opt] = false) || opts, {}) const viewer = new Cesium.Viewer('cesium_container', disable_opt) fetch('/project') .then(res => res.json()) .then(({ anchor, url }) => { const position = Cesium.Cartesian3.fromDegrees(...anchor) const modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(position) const tileset = new Cesium.Cesium3DTileset({ url, modelMatrix }) tileset.readyPromise.then(() => viewer.zoomTo(tileset)) viewer.scene.primitives.add(tileset) }) </script> </div> </body> </html>在同一目錄下執行如下命令啟動服務後,瀏覽器訪問
localhost:5500即可瀏覽資料。node index.js