世界の測量

Sibling of "Relevant, Timely, and Accurate, " but much lighter and shorter ※自らの所属する組織の見解を示すものでない

自製ベクトルタイルをS3でホストしてGitHub Pagesから公開するまで

国土数値情報(バス停留所データ)ベクトルタイルの作成

国土数値情報(バス停留所データ)ベクトルタイルを作成するために、GeoRubyによるShapefileの文字コードのShapefileからUTF-8への変換(Late 2013) - 世界の測量 で準備したUTF-8文字コードShapefileデータを、vagrant-webmapsに入っているtilestache-seed.pyを使ってGeoJSON Tilesに変換した。コマンドラインオプションは、次のRakefileに記載している。

task :default do
  sh "tilestache-seed.py -b 34.710641 134.877354 35.769141 135.958849 -c tilestache.cfg -l point_test 12 13 14 15 16"
end

また、Rakefileが参照しているtilestache.cfgファイルの内容は次の通り。

{"cache": {
  "name": "Disk",
  "path": "/vagrant",
  "dirs": "portable",
  "gzip": []
  },
 "layers": {
  "point_test": {
    "provider": {"name": "vector", "driver": "ESRI Shapefile", "verbose": true,
      "parameters": { 
        "file": "/vagrant/point_test/src/P11-10_26-jgd-g_BusStop_utf8.shp"},
        "properties": ["P11_001"]
      }
    }
  }
}

ズームレベル13,14,15あたりまではそれなりに現実的な計算時間で手に入る。しかし、ズームレベル16を入手するために必要な時間は馬鹿らしいほど長い。この解決方法としては、ベクトルタイル作成アルゴリズムを根本的にMapReduce式に改善する*1か、クライアントサイドでの提示に overzooming の技術を導入すべきである。overzooming の導入を、将来のエントリで紹介することになるだろう。

タイルが出来た段階でのローカルホストでのテスト

タイルが出来た段階でのローカルホストでのテストには、Sinatraを使った。次のようなRakefileに必要な処理を埋め込んだ。なぜわざわざSinatraを使うかというと、file: スキームURLに対しては、ウェブブラウザがCORSせず、結果として file: スキームの上では、ベクトルタイルが表示されないからである。

require 'rubygems'
require 'sinatra/base'

class App < Sinatra::Base
  get '/' do
    File.open('index.html').read
  end

  get '/leaflet-hash.js' do
    content_type "text/javascript"
    File.open('leaflet-hash.js').read
  end

  get '/TileLayer.GeoJSON.js' do
    content_type "text/javascript"
    File.open('TileLayer.GeoJSON.js').read
  end

  get '/*/*/*.geojson' do
    headers({"Access-Control-Allow-Origin" => "*"})
    path = "#{params[:splat].join('/')}.geojson"
    if File.exist?(path)
      File.open(path).read
    else
      "{}"
    end
  end
end

task :default do
  App.run!
end

この段階でのスクリーンショットが次である。
f:id:hfu:20131229012816p:plain

CORS設定onでAWS S3にアップロードする

完成したベクトルタイルのうち、ズームレベル13,14,15のものをふつうにS3にアップロードした。スタティックなウェブサイトとして使えるように設定しているbucketに置いたので、http://www.handygeospatial.info/2013/12/27/{z}/{x}/{y}.geojsonという名前で参照できる状態になっている。ここで留意すべきなのは、GeoJSONタイルデータはCORS設定して置いておかないと、原因が分かりにくいエラーになやまされるということ。
AWS S3のCORS設定については、Enabling Cross-Origin Resource Sharing - Amazon Simple Storage Service に解説されている。具体的には、手元のS3 Bucketでは、次のように設定した。
f:id:hfu:20131229011919p:plain

ウェブ地図サイトをGitHub Pagesアップロード

ウェブ地図サイト(下記index.html及び各配布元から頂いたTileLayer.GeoJSON.js及びleaflet-has.jsの写し)をGitHub Pagesにアップロードした。

<!doctype html>
<html>
<head>
  <meta charset='UTF-8'>
  <meta name="viewport" 
   content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
  <meta name="apple-mobile-web-app-capable" content="yes"/>
  <meta name="apple-mobile-web-app-status-bar-style" 
   content="black-translucent" />
  <link rel="apple-touch-icon" 
   href="https://si0.twimg.com/profile_images/666603054/r.png"/>
  <title>vectiles from tilestache</title>
  <link rel='stylesheet' href='http://leafletjs.com/dist/leaflet.css'>
  <script src='http://leafletjs.com/dist/leaflet.js'></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
  <script src='TileLayer.GeoJSON.js'></script>
  <script src='leaflet-hash.js'></script>
  <style>
  html, body, #mapdiv {margin: 0; padding: 0; width: 100%; height: 100%;}
  </style>
</head>
<body>
  <div id='mapdiv'/>
  <script>
icon = L.icon({iconUrl: 'http://handygeospatial.github.io/mapsites/2013/12/13/maki/bus-24.png'});
map = new L.Map('mapdiv', {zoom: 13, center: [35.0249, 135.7658]});
hash = new L.Hash(map);
map.addLayer(new L.TileLayer(
  'http://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png',
  {attribution: '地理院タイル'}));
map.addLayer(new L.TileLayer.GeoJSON(
  'http://www.handygeospatial.info/2013/12/27/{z}/{x}/{y}.geojson',
  {attribution: '国土数値情報(バス停留所データ) 国土交通省', 
   minZoom: 12, maxZoom: 15},
  {style: { // not used
        "clickable": true,
        "color": "#F0D",
        "fillColor": "#0FD",
        "weight": 1.0,
        "opacity": 0.3,
        "fillOpacity": 0.2
    },
   onEachFeature: function(feature, layer) {
     layer.bindPopup(feature.properties.P11_001);
     layer.setIcon(icon);
   }
  }));
  </script>
</body>
</html> 

サイト

完成したいサイトは、次の通り
http://handygeospatial.github.io/mapsites/2013/12/27/
スクリーンショットも残しておく。
f:id:hfu:20131229012332p:plain

*1:tilestache-seed.pyは、サーバサイドベクトルタイルサーバのseed機能であるから、タイル区画ごとにデータベースを検索するモデルでも合理的であるが、本来、(静的な)ベクトルタイルの作成を目的とするのであれば、ポイントデータは1レコードはかならず1タイルに落ちるのであるから、MapReduce式にストリーム処理するほうが合理的である。