natural earth shp檔轉topo.json

Table of contents

背景

  • 當增加cambecc(2016) earth套件的解析度時,會需要有較詳盡的底圖做為參考基準。這項作業的緣起是想重置earth套件的底圖,在海岸線及湖泊外加上省(中國範圍)、縣(臺灣地區)等行政區向量圖,以備未來套用高解析度數據。
  • cambecc(2016)提供的向量底圖網址已經失效,同樣的內容改在githubnaturalearthdata官網提供。
    • 前者可供一次性全下載(約16GB):git clone https://github.com/nvkelso/natural-earth-vector.git
    • 後者可視實際需要選取下載個別(zip)檔案。
  • Natural Earth(NE)目前為wrf-python等繪圖程式內設之公開底圖資料來源。其內容除自然(海岸線、河川、湖泊)之外,亦有文化類如行政區等疆界底圖。詳細解析度如表所示。
大類次類細類說明
自然疆界(physical)   
文化疆界(cultural)Admin 0 – Countries258 countries 
-Admin 0 – Countries point-of-views246~258 countries國家主要領土(with Taiwan)
-Admin 0 – Details199 issue passports.個別國家所有領土(with Taiwan)
-Admin 0 – Boundary Lines199 issue passports.含領海(with Taiwan)
-Admin 0 – Breakaway, Disputed Areas 主權爭議地區
-Admin 1 – States, Provinces 各國轄下之州、省範圍
-Admin 2 – Counties 美國各郡
-Populated Places 主要城市包括大多數州(省)治
  • 有關底圖的解析度:雖然NE提供到10m解析度的shape檔,但在earth公開的套件中,隱含了內設的繪圖解析度,因此如果放大解析度時會出現鋸齒問題。
    • 原設定圖面最大倍率為3000。
    • 由其滑鼠點到位置的座標小數點到0.01來看,其解析度應為0.01度,約為1公里。
    • 這可以說明何以其海岸線會出現鋸齒狀。
    • 依據cambecc(2016)之說明,此舉為topojson這支程式壓縮過程的結果
  • topojson檔案的內容樣式特色是
    1. 較shape檔、geojson檔更加精練、檔案也較小,有存取便捷的特色。
    2. 以物件為主的json檔,而不是以座標為主。
    3. 一般性的點、線、多邊形等幾何特徵(如shape檔案中的物件)轉變成點、線、弧等單元,
    4. 座標系統由原來離散點的經緯度浮點實數,轉變成整數、且據關連性的indexing。
    5. topojson轉換程式是OGR gdal套件中的一個程式,安裝時會複製到/usr/bin/topojson,其使用詳topojson --help

使用gdal套件轉換

  • cambecc(2016)建議使gdal套件來轉換.shp檔到.json檔、最後由topojson轉換程式轉成earth-topo.json檔案。經增修其腳本如下:

oo.cs腳本

#kuang@node03 /nas1/Data/javascripts/D3js/earthCWB/public/data/topo
#$ cat oo.cs
for res in 10 50 110;do
for itm in coastline populated_places ;do
  ogr2ogr -f GeoJSON ${itm}_${res}m.json ne_${res}m_${itm}.shp
done
done
for itm in lakes ;do
  ogr2ogr -f GeoJSON -where "scalerank < 4" ${itm}_10m.json ne_10m_${itm}.shp
  ogr2ogr -f GeoJSON -where "scalerank < 4" ${itm}_50m.json ne_50m_${itm}.shp
  ogr2ogr -f GeoJSON -where "scalerank < 2 AND admin='admin-0'" ${itm}_110m.json ne_110m_${itm}.shp
done
itm=rivers_lake_centerlines_scale_rank
  ogr2ogr -f GeoJSON ${itm}_50m.json ne_50m_${itm}.shp
  ogr2ogr -f GeoJSON -simplify 1 ${itm}_tiny.json ne_110m_$itm.shp
riv=${itm/s_lake_centerlines_scale_rank/}
mv ${itm}_tiny.json ${riv}_tiny.json

itm=populated_places
for i in $(ls ${itm}*.json);do
  j=${i/${itm}/city}
  mv $i $j
done

ogr2ogr -f GeoJSON -simplify 1 coastline_tiny.json ne_110m_coastline.shp
ogr2ogr -f GeoJSON -simplify 1 -where "scalerank < 2 AND admin='admin-0'" lakes_tiny.json ne_110m_lakes.shp

ogr2ogr -f GeoJSON bounds_10m.json ne_10m_admin_1_states_provinces_lines.shp
ogr2ogr -f GeoJSON bounds_110m.json ne_110m_admin_0_boundary_lines_land.shp
ogr2ogr -f GeoJSON -simplify 1 bounds_tiny.json ne_110m_admin_0_boundary_lines_land.shp

topojson  --q1 100000 -o earth-topo.json \
coastline_10m.json coastline_50m.json coastline_110m.json \
lakes_10m.json lakes_50m.json lakes_110m.json \
river_50m.json river_tiny.json \
city_10m.json city_110m.json \
bounds_10m.json bounds_110m.json bounds_tiny.json
  • 增加城市、河川、行政區界
  • 還需修改的.js程式如下

earth.js

  • 畫面提交後會出現底圖,主要是在函數buildRenderer內控制
  • 定義變數。原僅呼叫海岸線及湖泊,以下增加((…)內為json檔內之變數名稱):
    1. 河川(rivers_lake_centerlines)
    2. 城市(populated_places)
    3. 行政區界(admin_1_states_provinces_lines)
...
        var path = d3.geo.path().projection(globe.projection).pointRadius(7);
        var coastline = d3.select(".coastline");
        var river = d3.select(".rivers_lake_centerlines");
        var lakes = d3.select(".lakes");
        var city = d3.select(".populated_places");
        var bound= d3.select(".admin_1_states_provinces_lines");
        d3.selectAll("path").attr("d", path);  // do an initial draw -- fixes issue with safari
...
  • 在停等滑鼠動作過程的起、迄點,呼叫到前述項目的最小、與最大解析度條件
...
        // Attach to map rendering events on input controller.
        dispatch.listenTo(
            inputController, {
                moveStart: function() {
                    //river.datum(mesh.riverLo);
                    coastline.datum(mesh.coastLo);
                    lakes.datum(mesh.lakesLo);
                    //city.datum(mesh.cityLo);
                    //bound.datum(mesh.boundLo);
                    rendererAgent.trigger("start");
                },
                move: function() {
                    doDraw_throttled();
                },
                moveEnd: function() {
                    //river.datum(mesh.riverHi);
                    coastline.datum(mesh.coastHi);
                    lakes.datum(mesh.lakesHi);
                    //city.datum(mesh.cityHi);
                    //bound.datum(mesh.boundHi);
                    d3.selectAll("path").attr("d", path);
                    rendererAgent.trigger("render");
                },
                click: drawLocationMark
            });
...
  • 關閉(放棄)這些新增項目的原因
    1. cambecc(2016)似乎不接受線段之geometry,還是未給定顏色遭覆蓋,原因不明,新增項目均為黑影、且線段頭尾相連成為多邊形。
    2. 即使點狀之城市也為黑點,且大小不隨滑鼠縮放動作而異。
  • 讀取解析度上下界
...
    /**
     * @param resource the GeoJSON resource's URL
     * @returns {Object} a promise for GeoJSON topology features: {boundaryLo:, boundaryHi:}
     */
    function buildMesh(resource) {
        var cancel = this.cancel;
        report.status("Downloading...");
        return µ.loadJson(resource).then(function(topo) {
            if (cancel.requested) return null;
            log.time("building meshes");
            var o = topo.objects;
            var coastLo = topojson.feature(topo, µ.isMobile() ? o.coastline_tiny : o.coastline_110m);
            var coastHi = topojson.feature(topo, µ.isMobile() ? o.coastline_110m : o.coastline_10m); //50m -> 10m
            var riverLo = topojson.feature(topo, µ.isMobile() ? o.river : o.river_50m);
            var riverHi = topojson.feature(topo, µ.isMobile() ? o.river_50m : o.river_50m);
            var lakesLo = topojson.feature(topo, µ.isMobile() ? o.lakes_tiny : o.lakes_110m);
            var lakesHi = topojson.feature(topo, µ.isMobile() ? o.lakes_110m : o.lakes_10m);
            var cityLo = topojson.feature(topo, µ.isMobile() ? o.city_tiny : o.city_110m);
            var cityHi = topojson.feature(topo, µ.isMobile() ? o.city_110m : o.city_10m);
            var boundLo = topojson.feature(topo, µ.isMobile() ? o.bounds_tiny : o.bounds_110m);
            var boundHi = topojson.feature(topo, µ.isMobile() ? o.bounds_110m : o.bounds_10m);
            log.timeEnd("building meshes");
            return {
                coastLo: coastLo,
                coastHi: coastHi,
                riverLo: riverLo,
                riverHi: riverHi,
                lakesLo: lakesLo,
                lakesHi: lakesHi,
                cityLo: cityLo,
                cityHi: cityHi,
                boundLo: boundLo,
                boundHi: boundHi,
            };
        });
    }

globes.js

  • 有3處定義需注意新增:
    1. function standardGlobe() {defineMap: function(mapSvg, foregroundSvg)
    2. function orthographic() {defineMap: function(mapSvg, foregroundSvg)
    3. function waterman() {defineMap: function(mapSvg, foregroundSvg)

GeoJson版本

  • 由於earth套件中不能接受多邊形之geometry,乃構想將shape檔中的多邊形轉換成之GeoJson,直接併入EN json檔中再進行轉換。

shape to GeoJSON

  • mbostock, Mike Bostock製作了一個好用的命令列執行檔。
  • 轉換方式
    1. shape檔轉GeoJson可以借助線上轉檔服務。需提供.shp、.shx、.pro、.dbf等4個檔。
    2. 或直接取得臺灣縣市界的geoJSON檔案(eg. G0V@github)
    3. 只是前述作法仍需注意年代版本,版本如果太舊會無法與現況對應。因內政部不時更新數據,還是以最新版本shape檔進行轉換為宜。
  • 轉成(或取得)geometry皆為MultiPolygon或polygon,與EN海岸線屬同一性質。

coastline_10m.json 檔案之整理合併

  1. 將coastline_10m.json檔案中臺灣部分(及離島)予以去除,以避免重疊干擾。(搜尋經緯度、整批物件一併去除)
  2. 將前述轉成(取得)GeoJson檔案中features的物件,貼在coastline_10m.json檔案後面,(features是個序列,要記得逗點區格)。
  3. 重新進行oo.cs當中的topojson動作,合併各個json檔。
  4. 輸出點數(--q1)會影響物件的解析度。內設值為10K點,會得到約4Km的解析度,如增加為100K點,可以得到約400m的解析度,即使放大到縣市範圍(scaleExtent=60K)尚可接受。
topojson --help
 ...
 --q1, --post-quantization      maximum number of differentiable points along either dimension               [default: 10000]

結果

GeoJson版本.png
圖 以g0v之twCounty2010.geo.json置換臺灣島海岸線之成果