劃一體化實(shí)踐)
簡介Gosmore是一款基于OpenStreetMap數(shù)據(jù)的開源導(dǎo)航應(yīng)用面向需要離線地圖服務(wù)、看重自主可控的移動用戶與開發(fā)者也可作為學(xué)習(xí)地圖渲染與路徑規(guī)劃技術(shù)的入門范例。它提供2D/3D地圖顯示、目的地搜索和逐行路徑規(guī)劃核心優(yōu)勢在于將地圖數(shù)據(jù)轉(zhuǎn)為高效二進(jìn)制格式無需網(wǎng)絡(luò)也能流暢操作同時(shí)以直觀的多視角展示幫助用戶更好地理解地形與周圍環(huán)境。壓縮包共22個(gè)文件體積6.2MB主要包含主程序gosmore.exe、運(yùn)行所需的libxml2/libstdc等動態(tài)庫、default.pak與icons.csv等配置數(shù)據(jù)以及多段轉(zhuǎn)彎提示音wav音頻文件類型覆蓋完整便于本地部署與二次開發(fā)。目前已有45人學(xué)習(xí)下載。借助此包可直接啟動程序體驗(yàn)各類地圖操作開發(fā)者還可對照開源代碼理解離線地圖讀取、路徑計(jì)算和提示音頻觸發(fā)的實(shí)現(xiàn)思路并自定義地圖樣式、圖標(biāo)與語音提示既有實(shí)用價(jià)值也是學(xué)習(xí)開源導(dǎo)航項(xiàng)目的良好范例。1. 做離線地圖服務(wù)半年后我為什么回頭研究Gosmore大概半年前我接了一個(gè)內(nèi)部GIS項(xiàng)目需求很樸素在完全沒有外網(wǎng)的環(huán)境里渲染一套OpenStreetMap風(fēng)格的底圖同時(shí)支持點(diǎn)對點(diǎn)路徑規(guī)劃。市面上的方案看著很多MapLibre配矢量瓦片、TileServer配柵格瓦片、Nominatim做地名檢索一整套下來光是數(shù)據(jù)預(yù)處理就夠折騰幾周。后來在翻OSM生態(tài)的舊倉庫時(shí)注意到了Gosmore這個(gè)開源項(xiàng)目——一個(gè)幾乎不怎么更新的C地圖渲染引擎卻同時(shí)把渲染和路徑規(guī)劃兩件事都做了。先說說這個(gè)項(xiàng)目的基本定位給還不熟悉的朋友。Gosmore是一個(gè)基于OpenStreetMap數(shù)據(jù)的開源地圖引擎最開始由Nic Roets維護(hù)核心能力是把OSM的PBF格式原始數(shù)據(jù)直接吃進(jìn)去既能渲染出PNG格式的地圖瓦片也能做基于路網(wǎng)的最短路徑計(jì)算。它最吸引我的地方是渲染和路由共用同一套內(nèi)存數(shù)據(jù)不用像常規(guī)方案那樣維護(hù)兩套索引對于我這種追求部署簡潔的人來說簡直是一股清流。不過用下來的真實(shí)感受是這個(gè)項(xiàng)目的文檔和社區(qū)討論少得可憐能搜到的基本就是GitHub倉庫那份幾十行的README和一些十幾年前論壇帖子。本文就把我踩過的坑、實(shí)測過的參數(shù)、以及最終怎么把它跑進(jìn)生產(chǎn)環(huán)境的過程整理出來給想搞離線地圖渲染或者研究OSM數(shù)據(jù)處理的讀者一條更容易走的路。適用人群也明確一下如果你只是想在網(wǎng)頁上展示一張地圖MapLibre或者Leaflet配在線瓦片就夠了Gosmore不是給你用的。但如果你需要完全離線、低資源消耗、且希望渲染和路徑規(guī)劃一體化的方案Gosmore值得花一個(gè)下午來挖一挖。2. Gosmore的技術(shù)內(nèi)核一條命令處理PBF渲染和路由共用一套數(shù)據(jù)Gosmore的核心就一個(gè)二進(jìn)制文件加上一個(gè)配置文件。它沒有像PostGIS那樣的數(shù)據(jù)庫依賴也沒有瓦片緩存服務(wù)這種獨(dú)立進(jìn)程工作方式非常直接。2.1 本地?cái)?shù)據(jù)構(gòu)建把PBF變成內(nèi)存數(shù)據(jù)結(jié)構(gòu)我用的數(shù)據(jù)是中國區(qū)域的地圖數(shù)據(jù)從Geofabrik下載的china-latest.osm.pbf大約700MB左右。Gosmore的處理方式和很多渲染引擎不同它不直接識別PBF進(jìn)行逐層查詢而是需要先進(jìn)行一次構(gòu)建把PBF轉(zhuǎn)換成一個(gè)緊湊的自定義二進(jìn)制數(shù)據(jù)文件這個(gè)過程叫build。實(shí)際執(zhí)行命令大概是這樣的./gosmore-build /path/to/china-latest.osm.pbf new這個(gè)“new”參數(shù)表示從零構(gòu)建。構(gòu)建完成后會生成一個(gè)類似gosmore.dat的文件這個(gè)文件就是后續(xù)渲染和路由查詢的唯一數(shù)據(jù)源。這里有一個(gè)小細(xì)節(jié)值得注意構(gòu)建過程中內(nèi)存峰值比較高我最初在一臺只有4GB內(nèi)存的云主機(jī)上跑RSS直接沖到接近3.5GB嚇得我趕緊加了swap。建議至少準(zhǔn)備8GB內(nèi)存或者盡量在內(nèi)存充裕的臺式機(jī)上完成構(gòu)建然后把生成的gosmore.dat拷貝到目標(biāo)機(jī)器。生成后的文件在磁盤上大約2GB左右加載到內(nèi)存之后通過mmap方式映射實(shí)際常駐內(nèi)存遠(yuǎn)小于文件體積大約是文件大小的八分之一到十分之一。2.2 渲染和路由為什么能共用一份數(shù)據(jù)很多玩過地圖渲染的朋友會問渲染需要的是幾何信息和標(biāo)簽信息路由需要的是路網(wǎng)拓?fù)浜头较蛳拗七@兩套邏輯完全不同Gosmore怎么做到共用一套索引的原因在于Gosmore的數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)。它把OSM的節(jié)點(diǎn)、路徑和關(guān)系統(tǒng)一組織成了一棵樹樹的葉子節(jié)點(diǎn)保存坐標(biāo)和標(biāo)簽路徑的幾何信息在需要時(shí)通過節(jié)點(diǎn)序列動態(tài)重建。對于路由來說它根據(jù)Highway標(biāo)簽從同一棵樹里篩選可行駛的邊構(gòu)建出用于A*搜索的圖結(jié)構(gòu)。這樣設(shè)計(jì)的好處很直觀一份數(shù)據(jù)進(jìn)內(nèi)存兩條邏輯共用省去數(shù)據(jù)冗余和同步的一致性維護(hù)。代價(jià)是查詢效率不如專門的索引結(jié)構(gòu)。渲染時(shí)如果每個(gè)瓦片都去遍歷樹性能會很難看。Gosmore的做法是在渲染請求進(jìn)來時(shí)只定位到當(dāng)前坐標(biāo)范圍對應(yīng)的樹節(jié)點(diǎn)區(qū)間再做局部遍歷實(shí)測在普通筆記本上256x256瓦片的渲染時(shí)間可以控制在50到200毫秒之間對于離線低頻刷新場景完全夠用。2.3 與常見的瓦片方案對比我用過的完整鏈路是PostGIS加載OSM數(shù)據(jù)然后用Mapnik渲染瓦片。Gosmore的定位明顯偏向輕量化和嵌入式數(shù)據(jù)預(yù)處理時(shí)間PostGIS導(dǎo)入全國數(shù)據(jù)需要幾小時(shí)Gosmore構(gòu)建只需幾十分鐘運(yùn)行依賴PostGIS方案要數(shù)據(jù)庫和渲染服務(wù)Gosmore就一個(gè)二進(jìn)制加一個(gè)文件查詢靈活性PostGIS可以用SQL做任意空間分析Gosmore只能做渲染和路徑規(guī)劃所以我的判斷是Gosmore并不是要替代傳統(tǒng)GIS方案而是填補(bǔ)了“我要快速看個(gè)地圖、快速算個(gè)路線”的空檔。對于原型驗(yàn)證、嵌入式設(shè)備、離線平板地圖這類場景它的輕量優(yōu)勢很明顯。3. 編譯實(shí)錄依賴問題和內(nèi)存尖峰官方文檔不會告訴你的細(xì)節(jié)Gosmore官方倉庫在GitHub上能找到但這個(gè)項(xiàng)目的維護(hù)節(jié)奏基本屬于“能用就行”編譯環(huán)境變動很容易折騰人。我把我在Ubuntu 22.04上的完整編譯過程寫下來包括遇到的問題和解決方式。3.1 環(huán)境準(zhǔn)備與依賴安裝Gosmore本身依賴的第三方庫不多核心是幾個(gè)基礎(chǔ)庫和圖形庫。官方README列了需要安裝的軟件包但是很零散我這里整理一份完整的sudo apt install g make cmake libpng-dev libjpeg-dev \ libfreetype6-dev libfontconfig1-dev libgl1-mesa-dev \ libglu1-mesa-dev freeglut3-dev mesa-common-dev \ libxml2-dev libz-dev如果你想在無圖形環(huán)境下跑渲染輸出比如服務(wù)器端生成PNGx11相關(guān)的庫其實(shí)用不上但編譯時(shí)由于代碼里包含一些窗口渲染的邏輯所以mesa相關(guān)的開發(fā)包最好還是裝齊否則編譯會在某些頭文件處直接失敗。3.2 編譯過程中的三個(gè)坑先說第一個(gè)坑編譯器版本太新導(dǎo)致代碼兼容問題。我用GCC 11.3編譯某些老代碼對隱式類型轉(zhuǎn)換的要求更嚴(yán)格編譯直接報(bào)錯(cuò)。錯(cuò)誤信息類似cannot convert std::string to const char*。處理方式比較簡單把對應(yīng)的源碼文件里的.c_str()補(bǔ)齊就行總共改了6處不需要動邏輯。第二個(gè)坑PBF解析庫的版本選擇。Gosmore倉庫里自帶了一個(gè)pbf_parser目錄代碼是混在項(xiàng)目里的不需要單獨(dú)安裝libosmpbf-dev或protobuf。但如果系統(tǒng)里已經(jīng)裝了新版的protobuf編譯時(shí)頭文件搜索順序可能會導(dǎo)致沖突。解決辦法是在CMakeLists里把include_directories的順序調(diào)整一下讓項(xiàng)目自帶的頭文件優(yōu)先。第三個(gè)坑是內(nèi)存限制。前文提過構(gòu)建數(shù)據(jù)時(shí)內(nèi)存峰值問題編譯階段其實(shí)也有一個(gè)類似情況。如果使用-j$(nproc)并行編譯多個(gè)編譯單元同時(shí)吃內(nèi)存4GB的機(jī)器有概率被殺掉進(jìn)程。我最終是限制4個(gè)并行任務(wù)完成的編譯make -j4編譯成功后會生成gosmore可執(zhí)行文件和一個(gè)用于構(gòu)建數(shù)據(jù)的gosmore-build。兩個(gè)文件的區(qū)別只是在編譯宏上本質(zhì)上同一套代碼。3.3 Windows和Android的編譯說明Gosmore老早前支持Windows編譯用MinGW或者Visual Studio的解決方案文件都有但這些年沒怎么更新新系統(tǒng)下經(jīng)常編譯失敗。如果只是想在Windows上快速跑起來我建議用WSL2里的Linux環(huán)境編譯省去一堆煩人的配置。Android方面項(xiàng)目里確實(shí)有一個(gè)Android工程目錄可以做APK構(gòu)建但它依賴的SDK版本較老如果不是有定制的嵌入式需求不推薦花時(shí)間在上面。我的結(jié)論是Gosmore最舒服的運(yùn)行環(huán)境就是Linux服務(wù)器或者樹莓派這類設(shè)備。4. 渲染效果與樣式調(diào)整配置文件里如何控制出圖Gosmore渲染出的地圖并不會自動帶上OSM那種完整的漂亮樣式而是需要你手動賦予標(biāo)簽對應(yīng)的顯示規(guī)則。這里涉及的配置文件在示例包里叫g(shù)osmore.xml里面核心邏輯是按照標(biāo)簽條件設(shè)置顏色、線寬、字體和繪制層級。4.1 一個(gè)最簡單的渲染規(guī)則例子rules rule condition khighway vprimary/ line color#fcd6a5 width6/ line color#ffffff width3 casingtrue/ /rule rule condition kbuilding v*/ polygon fill#d9d0c9/ /rule /rules這個(gè)規(guī)則的含義很容易理解當(dāng)元素的tags里存在highwayprimary時(shí)繪制兩層線條第一層是6像素寬的橙色底第二層是3像素寬的白色中線形成類似描邊的效果。building標(biāo)簽則統(tǒng)一填充一個(gè)灰色。4.2 標(biāo)簽沖突和顯示等級的處理在地圖渲染里標(biāo)簽和道路之間的遮擋是永恒的問題。Gosmore的處理策略很簡單粗暴——按配置里規(guī)則的先后順序決定繪制優(yōu)先級。先匹配到的規(guī)則先繪制后匹配到的規(guī)則覆蓋在上面。實(shí)際使用中建議把次要道路的線寬放窄并放在配置靠前的位置主要道路放后面這樣主干道始終擁有更高的視覺優(yōu)先級。對于文字標(biāo)簽Gosmore根據(jù)元素的類型和縮放級別決定是否顯示。如果某個(gè)級別的文字太密集只能通過調(diào)整minzoom屬性過濾沒有自動避讓算法。這一點(diǎn)在成品地圖軟件里不可想象但對于自用或工具類場景完全可以接受。4.3 我調(diào)出來的一個(gè)實(shí)用配色方案我花了兩天時(shí)間對照OSM標(biāo)準(zhǔn)配色在Gosmore上實(shí)現(xiàn)了一套簡化的類OSM配色。關(guān)鍵點(diǎn)如下水系使用#aad3df填充邊界用#9cc0c8描邊植被使用#cdebb0填充道路按等級區(qū)分高速#e892a2、主干道#fcd6a5、次干道#ffffff、居住區(qū)道路#eeeeee鐵路使用黑白相間的虛線這套配置已經(jīng)把全國底圖的視覺效果調(diào)整到可用狀態(tài)可以在倉庫里搜索gosmore-style.xml獲取完整版本這里不展開全部代碼。5. 路徑規(guī)劃實(shí)測從北京到上海它算得怎么樣路徑規(guī)劃是Gosmore的隱藏亮點(diǎn)因?yàn)樗膶?shí)現(xiàn)并不像渲染那樣需要額外配置構(gòu)建好數(shù)據(jù)后直接調(diào)用同一個(gè)引擎即可。它內(nèi)置了基于A*的尋路算法支持機(jī)動車和步行兩種模式。5.1 如何調(diào)用路徑規(guī)劃接口Gosmore的路徑規(guī)劃入口通常是一個(gè)route函數(shù)傳入起止經(jīng)緯度返回一串途經(jīng)點(diǎn)。倉庫里自帶幾個(gè)示例程序其中有一個(gè)gosmore-test可以直接在命令行里測試./gosmore-test route 116.40 39.90 121.47 31.23這里的坐標(biāo)是北京的經(jīng)度緯度后面是上海的。運(yùn)行后會輸出一系列坐標(biāo)點(diǎn)把這些點(diǎn)連接起來就是規(guī)劃出的路線。5.2 實(shí)測結(jié)果路線質(zhì)量與性能我實(shí)測了多條路線包括北京到上海、成都到西安、廣州到深圳。單次路徑規(guī)劃的時(shí)間在幾十毫秒以內(nèi)非???。路線質(zhì)量上Gosmore傾向于選擇主干道和高速但并不會自動考慮實(shí)時(shí)交通信息所以只能作為沒有實(shí)時(shí)路況下的靜態(tài)路線方案。有一個(gè)細(xì)節(jié)需要注意Gosmore的路徑規(guī)劃是基于OSM的way類別來判斷可通行性的。如果某條路在OSM里沒有被標(biāo)記為highway那么即使地圖渲染出來了這條線路徑規(guī)劃也不會把它當(dāng)作可行駛道路。所以當(dāng)規(guī)劃結(jié)果出現(xiàn)“繞路”或“無路可走”的情況優(yōu)先檢查OSM數(shù)據(jù)里對應(yīng)區(qū)域的道路標(biāo)記是否完整而不是懷疑程序邏輯。5.3 路徑規(guī)劃輸出格式的二次處理Gosmore默認(rèn)輸出的是一個(gè)坐標(biāo)點(diǎn)列表如果想在Leaflet或者其他前端地圖上展示需要自己把這些點(diǎn)編碼成GeoJSON或者GPX格式。我寫了一段Python腳本做轉(zhuǎn)換核心是把輸出的文本解析成JSON數(shù)組然后拼成一個(gè)LineStringimport json def parse_gosmore_route(raw): points [list(map(float, line.split())) for line in raw.strip().splitlines()] return { type: Feature, geometry: { type: LineString, coordinates: points }, properties: {} }這段腳本雖然簡單但配合后端接口就能快速做成一個(gè)點(diǎn)對點(diǎn)規(guī)劃服務(wù)。6. 與主流地圖渲染方案對比以及我對Gosmore維護(hù)狀態(tài)的判斷很多人會把Gosmore和MapLibre、Mapbox GL Native、Valhalla這些近幾年更活躍的項(xiàng)目直接對比。我在這里把差異點(diǎn)列出來供你在選型時(shí)參考。6.1 方案橫向?qū)Ρ染S度GosmoreMapLibre GL JS 矢量瓦片Valhalla路由部署復(fù)雜度極低單文件需要瓦片生成工具鏈需要構(gòu)建路網(wǎng)圖渲染質(zhì)量偏OSM經(jīng)典風(fēng)格配置有限強(qiáng)支持動態(tài)樣式和高性能交互不負(fù)責(zé)渲染路徑規(guī)劃內(nèi)置A*支持機(jī)動/步行不支持專業(yè)級多模式路由數(shù)據(jù)預(yù)處理PBF構(gòu)建一次需要Tilemaker/Planetiler切瓦片需要Valhalla構(gòu)建工具實(shí)時(shí)性無交暖和路況支持路況數(shù)據(jù)疊加支持限速和交通數(shù)據(jù)維護(hù)活躍度低幾年一更新高活躍社區(qū)較高6.2 一些坦誠的局限Gosmore這些局限你必須知道矢量瓦片方面它本來就不支持輸出矢量數(shù)據(jù)所以前端交互像點(diǎn)擊彈出屬性、圖層開關(guān)這類功能都無法實(shí)現(xiàn)它只輸出PNG柵格瓦片。多語言標(biāo)簽渲染依賴字體中文字體需要額外配置默認(rèn)字體只覆蓋拉丁字符所以中文地區(qū)需要手動指定中文字體路徑否則地圖上的中文標(biāo)簽會是方塊。數(shù)據(jù)庫查詢方面它不支持空間關(guān)系查詢像“找出某個(gè)點(diǎn)周圍500米的醫(yī)院”這種操作它做不了需要另接引擎。這些局限決定它適合做底圖顯示和基礎(chǔ)路線不適合做復(fù)雜的GIS分析。6.3 我對它是否值得用的最終判斷從維護(hù)時(shí)間線看Gosmore的核心提交基本停留在幾年前最近只是零星的兼容性修復(fù)這意味著它短期內(nèi)不太可能加入什么新功能。但地理數(shù)據(jù)領(lǐng)域有個(gè)特點(diǎn)底層數(shù)據(jù)格式穩(wěn)定引擎的核心邏輯很難過時(shí)。我個(gè)人的使用體驗(yàn)是只要OSM的PBF格式不推翻重來Gosmore現(xiàn)有的渲染和路由能力就能一直用下去尤其是在“數(shù)據(jù)完全離線、資源受限、能跑Linux”的部署場景里它比那些需要一堆依賴的新項(xiàng)目省心得多。如果你需要的是一套可以快速部署、穩(wěn)定運(yùn)行、不追求花哨交互的離線地圖服務(wù)Gosmore是一個(gè)值得嘗試的選擇。如果你需要實(shí)時(shí)交通、復(fù)雜圖層交互或者活躍的社區(qū)支持那還是把目光轉(zhuǎn)向MapLibre或Valhalla那套組合吧。最后分享一個(gè)小經(jīng)驗(yàn)部署時(shí)建議把gosmore.dat放在SSD上首次啟動時(shí)加載速度差異很大另外給進(jìn)程設(shè)置ulimit -n調(diào)高文件描述符限制能避免高并發(fā)瓦片請求時(shí)出現(xiàn)打開文件數(shù)不足的問題。我上線服務(wù)后用Go寫了個(gè)簡單的瓦片服務(wù)器包了一層整體運(yùn)行至今非常穩(wěn)定。本文還有配套的精品資源點(diǎn)擊獲取