世界の測量

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

リアルタイム・ベクトルタイル

リアルタイム・ベクトルタイルの実験をした。結果は http://handygeospatial.github.io/mapsites/2014/02/25/

タイルデータサーバ

heroku で実装した。http://rtvt.herokuapp.com/xyz/11/1852/749.geojson といったアドレスにアクセスすれば、タイル内10点のランダムデータを返すというものだ。
heroku の具体的な使い方については、 http://i2bskn.hateblo.jp/entry/2013/06/11/000625 を参考にさせていただいた。
各ファイルの内容を紹介する。

app.rb

メインプログラム。

require 'bundler/setup'
require 'sinatra/base'
require 'json'

class App < Sinatra::Base
  def latlng(z, x, y)
    n = 2.0 ** z
    lng = 360.0 * x / n  - 180.0
    lat_rad = Math::atan(Math::sinh(Math::PI * (1 - 2 * y / n)))
    lat = 180.0 * (lat_rad / Math::PI)
    {:lat => lat, :lng => lng}
  end

  def extent(z, x, y)
    lower_corner = latlng(z, x, y + 1)
    upper_corner = latlng(z, x + 1, y)
    {:lat => lower_corner[:lat], :lng => lower_corner[:lng],
     :width => upper_corner[:lng] - lower_corner[:lng],
     :height => upper_corner[:lat] - lower_corner[:lat]}
  end

  def geojson(n, z, x, y)
    e = extent(z, x, y)
    json = {:type => 'FeatureCollection', :features => []}
    n.times {|i|
      json[:features] << {:type => 'Feature',
        :geometry => {:type => 'Point',
          :coordinates => [e[:lng] + rand * e[:width],
            e[:lat] + rand * e[:height]]},
        :properties => {:number => i + 1}}
    }
    JSON.dump(json)
  end

  get '/' do
    'Hi.'
  end

  get '/xyz/*.*' do
    headers['Access-Control-Allow-Origin'] = '*'
    (z, x, y) = params[:splat][0].split('/').map{|v| v.to_i}
    ext = params[:splat][1]
    geojson(10, z, x, y)
  end
end

Gemfile

source 'https://rubygems.org'
ruby '2.0.0'
gem 'sinatra'
gem 'json'

Procfile

web: bundle exec rackup config.ru -p $PORT

config.rb

$:.unshift(File.dirname(__FILE__))
require 'app'
run App

ウェブアプリケーション

http://handygeospatial.github.io/mapsites/2014/02/25/ に置いた。ソースは次の通り。

index.html

<!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" />
  <title>リアルタイム・ベクトルタイル</title>
  <link rel='stylesheet' href='http://leafletjs.com/dist/leaflet.css'>
  <script src='http://leafletjs.com/dist/leaflet.js'></script>
  <script src="leaflet-hash.js"></script>
  <script src="TileLayer.GeoJSON.js"></script>
  <style>
  html, body, #mapdiv {margin: 0; padding: 0; width: 100%; height: 100%;}
  </style>
</head>
<body>
  <div id='mapdiv'/>
  <script>
function render_properties(prop) {
  var s = ''
  for(name in prop) {
    s += "<tr><td>" + name + "</td><td>" + prop[name] + "</td></tr>";
  }
  return "<table border='1'>" + s + "</table>";
}

icon = L.icon({iconUrl: 'http://cyberjapan.jp/symbols/129.bmp',
  iconSize: [20, 20]});
map = new L.Map('mapdiv', {zoom: 14, center: [35.6850, 139.7512]});
hash = new L.Hash(map);
map.addLayer(new L.TileLayer(
  'http://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png',
  {attribution: '地理院タイル'}));
layer = new L.TileLayer.GeoJSON(
  'http://rtvt.herokuapp.com/xyz/{z}/{x}/{y}.geojson',
  {attribution: 'リアルタイム・ベクトルタイル', unloadInvisibleTiles: true,
   minZoom: 11, maxZoom: 18},
  {style: {"clickable": true},
   onEachFeature: function(feature, layer) {
     layer.bindPopup(render_properties(feature.properties));
     layer.setIcon(icon);
   }
  });
map.addLayer(layer);
refresh = function() {layer.redraw(); setTimeout(refresh, 3000)};
refresh();
  </script>
</body>
</html>