ベクトルタイル・いちさと(小学校区)Pt. 5・クリップトTopoJSON
TopoJSONを使うと良いよという助言を頂いたので、TopoJSONタイルにチャレンジしてみた。
結果
※見た目は従前と変わらないが、データが軽くなったのでレスポンスがよくなっている。
http://handygeospatial.github.io/mapsites/2014/01/06/clipped_topojson.html
TopoJSONデータの作成
TopoJSONデータの作成は、mbostockさんのtopojsonコマンドラインツールを使う。
$ npm install -g topojson
でインストールが行われる。コマンドラインパラメータは、Command Line Reference · mbostock/topojson Wiki · GitHubで解説されている。この解説を参考にしながら、これまでに作ったGeoJSONタイルを変換するアプローチで、次のスクリプト(効率悪い...)で変換を行った。
# topo.rb CC0 require 'find' require 'fileutils' count = 0 {'school_district' => 'topojson_clipped', 'school_district_unclipped' => 'topojson_unclipped'}.each {|src, dst| Find.find(src) {|src_path| dst_path = src_path.sub('.geojson', '.topojson').sub(src, dst) FileUtils.mkdir(dst_path) if File.directory?(src_path) and !File.exist?(dst_path) if /geojson$/.match src_path json = `/usr/local/share/npm/bin/topojson -p -- es=#{src_path} | jq --ascii-output --compact-output '.' > #{dst_path}` count += 1 print "#{count}:@#{dst}" + json + "\n" end } }
topojson コマンドの「-p」オプションで属性をすべてコピーするようにした。
また、topojson コマンドが吐く JSON は日本語文字列をUnicodeエスケープしないので、jq コマンドを使ってUnicodeエスケープするようにした。あわせて、コンパクト化もjqに委ねた。
作成したTopoJSONタイルは、次の場所にホストしている。
サイト
TopoJSONタイルサイトの作成にあたっては、NelsonMinarさんの次のサイトを参考にした。
<!doctype html> <html> <head> <!-- thx https://gist.github.com/NelsonMinar/5851197 --> <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>ベクトルタイル・いちさと(小学校区)Clipped TopoJSON</title> <link rel='stylesheet' href='http://cdn.leafletjs.com/leaflet-0.7/leaflet.css'> <script src='http://cdn.leafletjs.com/leaflet-0.7/leaflet.js'></script> <script src="http://handygeospatial.github.io/mapsites/2014/01/01/leaflet-hash.js"></script> <script src="http://d3js.org/d3.v3.min.js"></script> <script src="http://d3js.org/topojson.v1.min.js"></script> <script src="TileLayer.d3_topoJSON.js"></script> <style> html, body, #mapdiv {margin: 0; padding: 0; width: 100%; height: 100%;} path {stroke-linejoin: round; stroke-linecap: round; fill: none;} path.es {stroke: #00f; fill: #00f; strokeOpacity: 0.3; fill-opacity: 0.2;} </style> </head> <body> <div id='mapdiv'/> <script> map = new L.Map('mapdiv', {zoom: 13, center: [35.6822, 139.7386]}); hash = new L.Hash(map); // this is important. see https://gist.github.com/NelsonMinar/5851197 new L.geoJson({"type": "LineString","coordinates":[[0,0],[0,0]]}).addTo(map); map.addLayer(new L.TileLayer( 'http://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png', {attribution: '地理院タイル', minZoom: 13, maxZoom: 13})); map.addLayer(new L.TileLayer.d3_topoJSON( 'http://www.handygeospatial.info/2014/01/06/' + 'topojson_clipped/{z}/{x}/{y}.topojson', {class: 'es', attribution: '国土数値情報(小学校区データ) 国土交通省', minZoom: 13, maxZoom: 13, })); </script> </body> </html>
/* Experimental vector tile layer for Leaflet * Uses D3 to render TopoJSON. Derived from a GeoJSON thing that was * Originally by Ziggy Jonsson: http://bl.ocks.org/ZJONSSON/5602552 * Reworked by Nelson Minar: http://bl.ocks.org/NelsonMinar/5624141 * * Todo: * Make this work even if <svg> isn't in the DOM yet * Make this work for tile types that aren't FeatureCollection * Match D3 idioms for .classed(), .style(), etc * Work on allowing feature popups, etc. */ // with a minor change by hfu 20140107 (for(key in tjData.objects)) L.TileLayer.d3_topoJSON = L.TileLayer.extend({ onAdd : function(map) { L.TileLayer.prototype.onAdd.call(this,map); this._path = d3.geo.path().projection(function(d) { var point = map.latLngToLayerPoint(new L.LatLng(d[1],d[0])); return [point.x,point.y]; }); this.on("tileunload",function(d) { if (d.tile.xhr) d.tile.xhr.abort(); if (d.tile.nodes) d.tile.nodes.remove(); d.tile.nodes = null; d.tile.xhr = null; }); }, _loadTile : function(tile,tilePoint) { var self = this; this._adjustTilePoint(tilePoint); if (!tile.nodes && !tile.xhr) { tile.xhr = d3.json(this.getTileUrl(tilePoint),function(error, tjData) { if (error) { console.log(error); } else { for(var key in tjData.objects) { var geoJson = topojson.feature(tjData, tjData.objects[key]); tile.xhr = null; tile.nodes = d3.select(map._container).select("svg").append("g"); tile.nodes.selectAll("path") .data(geoJson.features).enter() .append("path") .attr("d", self._path) .attr("class", self.options.class) .attr("style", self.options.style) .append("title").text(function(d) { var s = ''; for(key in d.properties) { s += key + ': ' + d.properties[key] + '\n'; } return s;}); } } }); } } });
感想
TopoJSONを使うためにd3への依存性が強化されたことにより、結果としてd3的にSVGがブラウザネイティブで見えるようになってきた。これは正しい方向性であり、SVG符号化法にまつわる制約をTopoJSONで回避しつつ、SVGの強力な機能をウェブ地図に持ってくるよい方法である。まずは、次のドキュメントをよく身につけたい。
D3.js と TopoJSON で地図を作る
その前に、アンプリップトを試す必要もあるだろう。