[原]使用node-mapnik生成openstreetmap-carto風格的瓦片


上回說到如何在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) 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM