上回說到如何在CentOS上部署node-mapnik,本想着接下來學習如何使用node-mapnik生成openstreetmap的瓦片圖,沒想到在接下來的近40天的時間里忙成了狗!好不容易等到元旦終於有兩天屬於自已的時間了。經過一天時間折騰,終於可以初步實現node-mapnik生成openstreetmap-carto風格的瓦片了。
本文涉及的內容較多,而且要用到很多之前的文章中的內容,如有對其中某一個環節不明白,建議可先看前幾篇文章。
現在的情況是:
1. PostgreSQL和PostGis安裝好了;
2. 使用osm2pgsql將全中國的數據導入到PostGis中,庫名為:gis;
(要下載全中國地圖請到這里,如果是測試的話,建議不要下載過大的數據,不然導入到PostGis會非常耗時)
3. 安裝了mapnik和open-mapnik;
現在的需求是:
1. 使用node-mapnik渲染地圖;
2. 地圖樣式要和openstreetmap官方的一樣;
3. 能夠通過web方式獲取地圖瓦片;
下面詳細介紹實現方法
一、配置openstreetmap官方配圖樣式
openstreetmap官方的樣式下載是https://github.com/gravitystorm/openstreetmap-carto
建議下載百度網盤里的文件,因為官方最新(2017年1月1日)的版本有bug
鏈接:http://pan.baidu.com/s/1jHQwq9o 密碼:vgla
下載后先進行必要的配置
1. 在導入osm數據時指定樣式文件,請參考CentOS7部署osm2pgsql第六節內容。
2. 建立索引。
psql -d gis -f path/indexes.sql
3. 下載shape文件,這些文件包含世界地圖,當地圖級別低於9級時就不訪問數據庫,直接使用shape文件中數據了。
python scripts/get-shapefiles.py
不過不建議使用這個自動化工具下載,因為這些數據都在國外的服務器上,如果沒有梯子就直接下,估計你一個周末就黃了,最好的方式是下載下列網盤里面的文件。
world_boundaries-spherical.tgz
鏈接:http://pan.baidu.com/s/1kVsK7uJ 密碼:tdfc
ne_110m_admin_0_boundary_lines_land.zip
鏈接:http://pan.baidu.com/s/1slOwvvR 密碼:z7t0
simplified-land-polygons-complete-3857.zip
鏈接:http://pan.baidu.com/s/1mhQYmog 密碼:5tbz
land-polygons-split-3857.zip
鏈接:http://pan.baidu.com/s/1c2OgSYO 密碼:wdpt
antarctica-icesheet-polygons-3857.zip
鏈接:http://pan.baidu.com/s/1nvdNvcP 密碼:fzjl
antarctica-icesheet-outlines-3857.zip
鏈接:http://pan.baidu.com/s/1eSifwUA 密碼:isdm
下載好后,在openstreetmap-carto目錄中手工建一個名為 data 的文件夾,然后將這些文件放進行,一一解壓,就OK了。
4. 安裝Noto字體,安體下載地址是:https://noto-website-2.storage.googleapis.com/pkgs/Noto-hinted.zip
下載好后,相辦法傳到CentOS的 /usr/share/fonts 目錄中,然后:
cd /usr/share/fonts
sudo mkdir noto && cd noto sudo mv ../Noto-hinted.zip Noto-hinted.zip sudo unzip Noto-hinted.zip sudo yum install fontconfig -y fc-cache -fv
sudo chmod -R 755 /usr/share/fonts
sudo rm Noto-hinted.zip
這里就把基礎工作做好了,現在我們仔細看看這個openstreetmap-carto目錄里都有些什么
這里值得注意的是*.mss文件和project.mml文件,這些文件就是傳說中的TileMill生成的樣式文件,其中mml是項目文件,主要保存地圖的基本屬性、數據庫連接、圖層屬性等,mss則主要是各個圖層的樣式。
那我們該如何使用這些樣式呢?這就要稍說一下mapnik是如何工作的了。
二、配圖工具及准備樣式文件
mapnik提供編程的方式向一個Map對象中添加Layer對象,在這個過程中指定每個Layer的數據源以及樣式
C++示例
node-mapnik示例
我們思考一下,這樣好嗎?很顯然,如果采用這種方式,那么配置人員就需要具備編程能力,這顯然不合理,那好的方法是什么呢?最好是不編程,直接寫寫樣式文件就能配出一張圖來。當然,mapnik官方也是這么想的,所以就有了XML方式了。
XML示例
這樣看起來像點話了。但是做為一個和顏值有密切關系的工作,使用純文本開發貌似也挺不合理的,在這種情況下,當...當...當,圖形化的IDE就閃亮登場了。
TileMill
MapBox Studio Classic
上面我們提到的*.mss文件就是TileMill設計的樣式,project.mml是TileMill的項目文件。這樣就對上號了,openstreetmap-carto是TileMill設計出來的。那么這個MapBox Studio Classic又是什么呢?它和TileMill功能基本一致,也是設計CartoCSS樣式文件工具。不同的是MapBox Studio Classic生成的項目文件后綴名是yml,而且生成的要素也更齊全點。但是最大的問題是MapBox Studio Classic登錄帳戶需要Fq!(GFW威武!)。
從網上的資料來看,MapBox Studio Classic逐漸要取代TileMill
有了所見即所得的設計器,再加上表現力豐富、看着順眼的類CSS語法的CartoCSS,那配圖工作簡直So easy了!更贊的是MapBox提供了這么多的預置地圖樣式,是不是看着要流口水了\(^o^)/
好吧,那么現在唯一的問題是,找遍了openstreetmap-carto的文檔,就是沒有提如何使用!在這里就需要引入另一個工具了:Carto
安裝Carto
sudo npm install -g carto --registry=https://registry.npm.taobao.org
Carto可以將mml轉換為xml文件,方法如下:
carto project.mml > mapnik.xml
不過在轉換之前還有一個小小的事情要做,openstreetmap-carto源碼中默認的數據庫連接還沒有改過來,所以我們先指定PostGis的連接參數,然后再使用上面的命令生成xml樣式文件,這里請注意,要先確保openstreetmap-carto目錄有寫入權限。
修改前:
修改后:
生成的文件如下:
三、試驗生成openstreetmap-carto樣式的地圖
先在用戶目錄下生成項目文件夾,然后照着官方示例先來一發
cd ~ mkdir -p demoprojects/nodemapnik && cd demoprojects/nodemapnik
vim demo1.js
輸入以下內容,注意,load方法中的路徑要改相應的改下啊
var mapnik = require('mapnik'); var fs = require('fs'); // register fonts and datasource plugins mapnik.register_system_fonts(); mapnik.register_default_input_plugins(); var map = new mapnik.Map(256, 256); map.load('/home/postgresql_data/openstreetmap-carto/mapnik.xml', function(err,map) { if (err) throw err; map.zoomAll(); var im = new mapnik.Image(256, 256); map.render(im, function(err,im) { if (err) throw err; im.encode('png', function(err,buffer) { if (err) throw err; fs.writeFile('map.png',buffer, function(err) { if (err) throw err; console.log('saved map image to map.png'); }); }); }); });
然后運行一下
node demo1.js
生成的效果圖
Good,說明一切正常沒問題,接下來我們使用Web的方式生成瓦片圖。
四、創建Web瓦片服務,生成指定索引的地圖瓦片
1. 安裝必要的nodejs模塊
sudo npm install -g connect --registry=https://registry.npm.taobao.org
sudo npm install -g sphericalmercator --registry=https://registry.npm.taobao.org
2. 創建node.js應用程序(才開始接觸node.js,高手輕拍)
先進入項目目錄
cd ~/demoprojects/nodemapnik
創建服務器程序:app.js
var mapnik = require('mapnik') , mercator = require('sphericalmercator') , http = require('http') , url = require('url') , fs = require('fs') , path = require('path') , connect = require('connect'); mapnik.register_system_fonts(); mapnik.register_default_input_plugins(); var server = http.createServer(function(req, res) { console.log('server start...'); var query = url.parse(req.url.toLowerCase(), true).query; res.writeHead(500, { 'Content-Type': 'text/plain' }); console.log(query); if (!query || Object.keys(query).length == 0) { console.log('open html'); try { res.writeHead(200, { 'Content-Type': 'text/html' }); if (req.url == '/') { res.end(fs.readFileSync('./index.html')); } else { res.end(fs.readFileSync('./' + req.url)); } } catch (err) { res.end('Not found: ' + req.url); } } else { console.log('validate query'); if (query && query.x !== undefined && query.y !== undefined && query.z !== undefined ) { console.log('x: ' + query.x + ' y: ' + query.y + ' z: ' + query.z); var merc = new mercator({size:256}); var map = new mapnik.Map(256,256); var bbox = merc.bbox(parseInt(query.x), parseInt(query.y), parseInt(query.z), false, '900913'); console.log('begin load mapnik style'); map.loadSync('/home/postgresql_data/openstreetmap-carto/mapnik.xml'); console.log('load mapnik style success'); console.log('layers length is: ' + map.layers().length); map.zoomToBox(bbox); var img = new mapnik.Image(256,256); map.render(img, function(err, img) { if (err) { res.end(err.message); } else { console.log('begin render map'); res.writeHead(200, { 'Content-Type': 'image/png' }); console.log('x: ' + query.x + ' y: ' + query.y + ' z: '+ query.z); res.end(img.encodeSync('png')); console.log('res end'); } }); } } }); console.log('start...'); connect() .use(server) .listen(3000);
創建前端頁面:index.html
<!DOCTYPE html> <html> <head> <script src="https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/js/lib/jquery-1.10.2_d88366fd.js" type="text/javascript"></script> <script type="text/javascript"> $(function(){ $('#btn').click(function(){ var x = $('#x').val(); var y = $('#y').val(); var z = $('#z').val(); $('#msg').text('x=' + x + ' y=' + y + ' z=' + z); $('#root').empty(); $('#root').append('<img style="width:256px;height:256px" src="' + 'http://192.168.1.98:3000?x=' + x + '&y=' + y + '&z=' + z +'" alt="tile image" />'); }); }); </script> </head> <body> <div><span id="msg"></span></div> <div id="root" style="height: 256px;"> </div> <div> <input id="x" value="12162" style="width:50px"/> <input id="y" value="6664" style="width:50px"/> <input id="z" value="14" style="width:50px"/> <input id="btn" type="button" value="get tile"/> </div> </body> </html>
3. 然后在防火牆上打洞,讓3000端口可以通過,或者懶點的辦法,關了防火牆了事(我懶)。
systemctl stop firewalld
4. 啟動服務
node app.js
5. 使用瀏覽器打開網頁,http://服務器ip:端口號,然后點擊“get tile”按鈕。
初始界面
獲得瓦片,完美支持萬圖語(圖上中文左邊的文字)
與openstreetmap官網對比
服務器端
至此,使用node-mapnik生成瓦片算是完工了。網上相關資料實在太少了,本文算是解決了有沒有的問題,能力提升還要靠大家一起努力!如果大家在照着本文實驗的時候出現問題,請留言一起討論。
下一步的打算是:將openstreetmap-carto應用到生產環境中。架設openstreetmap生產環境的Tile Server。這涉及到node-mapnik切圖、瓦片緩存、node.js單機集群等好多問題,不過github上貌似有不少現成的東西可以用,真希望我能有多點時間學習研究。
轉載請注明原作者(think8848)和出處(http://think8848.cnblogs.com)