D3.jsを使ってCO2排出量を可視化してみる。
NASA EARTHのSizing Up the Carbon Footprint of Citiesがかっこよかったので、D3.jsの勉強を兼ねて、環境省のCO2排出量の現況推計のデータから、CO2排出量の可視化画像を作ってみました。
■成果物:日本のCO2排出量可視化画像(2015年度)
Sizing Up the Carbon Footprint of Cities https://t.co/XR9ZnG0H6h #NASA
— NASA Earth (@NASAEarth) April 11, 2019
市町村の境界データの作成(topojson)
最初に市町村境界のtopojsonを作成しました。 境界データは国土交通省の国土数値情報ダウンロードサービスから「行政区域」のデータをダウンロードし、QGISでポリゴンを簡略化した後、geojsonを出力しました。 その後、小さなエンドウ豆:topojsonへの変換と鉄道データの描画を参考に、geojsonをtopojsonに変換しました。
$ npm i -g topojson $ geo2topo -q 1e6 japan=japan2018.geojson > japan2018.topojson
CO2排出量データの作成
環境省:部門別CO2排出量の現況推計から2015年度CO2排出量データ、国土地理院:全国都道府県市区町村別面積調査から市町村面積データをダウンロードし、「市町村コード」と「単位面積当たり排出量」データをCSVファイルに整理しました。
D3.jsとLefletによるCO2排出量の可視化
Spark Frameworkで市町村単位の単位面積当たりCO2排出量(1000t-CO2/km2)を可視化しました。 地理情報はLeflet、情報の描画はD3.jsを使用しました。CO2排出量データはSpark FrameworkでCSVファイルを読み込んだ後、「市町村コード」と「描画色」の連想配列を作成し、geojson.propertiesの「市町村コード(N03_007)」から各市町村ポリゴンの描画色を呼び出しています。
$(document).ready(function(){ d3.json("geojson/japan2018.topojson").then(function(json) { draw(json); }); }); function draw(json){ "use strict"; console.log("456"); let geojson; if(json.type==="Topology"){ geojson = topojson.feature(json, json.objects.japan) } else { geojson = json; } let map = L.map('map').setView([39.702053 , 141.15448379999998], 5); let mapLink = '<a href="http://osm.org/copyright"> OpenStreetMap</a> contributors, <a href= "http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'; L.tileLayer('https://tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png', { attribution: '© '+mapLink+' contributors', minZoom: 4, maxZoom: 6, }).addTo(map); L.svg().addTo(map); let svg = d3.select("#map").select("svg"); let g = svg.append("g"); let transform = d3.geoTransform({point: projectPoint}); let path = d3.geoPath().projection(transform); let featureElement = svg.selectAll("path") .data(geojson.features) .enter() .append("path") .attr("fill", function(d,i){ return color(d,i); }) .attr("fill-opacity", 0.6); map.on("moveend", update); update(); function projectPoint(x, y) { var point = map.latLngToLayerPoint(new L.LatLng(y, x)); this.stream.point(point.x, point.y); } function update() { featureElement.attr("d", path); } function color(d,i){ return colorMap[d.properties.N03_007]; } }
package mapd3js; import static spark.Spark.get; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.util.HashMap; import java.util.Map; import java.util.Optional; import com.google.gson.Gson; import spark.ModelAndView; import spark.Spark; import spark.template.mustache.MustacheTemplateEngine; public class Main { public static void main(String[] args) { Optional<String> optionalPort = Optional.ofNullable(System.getenv("PORT")); optionalPort.ifPresent(p -> { int port = Integer.parseInt(p); Spark.port(port); }); Spark.staticFileLocation("/public"); Map<String,String> map=getColorMap(); Gson gson=new Gson(); get("/", (request, response) -> { Map<String, Object> model = new HashMap<>(); model.put("color", gson.toJson(map)); return new ModelAndView(model, "index.mustache"); }, new MustacheTemplateEngine()); } private static String getColor(double val){ int r=0; int g=(int)(255.0*val*val*Math.pow(1.0*val*val,1.0/(2.0*val))); int b=(int)(255.0*val*val*Math.pow(1.0*val*val,1.0/(2.0*val))); return "rgb("+Integer.toString(r)+","+Integer.toString(g) +","+Integer.toString(b)+")"; } }
画像処理
これだけだと少し味気ないので、Tellusの傾斜角陰影図と合成(差分)し、それっぽく調整しました。
雑感
これまで、情報可視化にはProcessing.jsを使用していました。
Processing.jsはJavaライクでとても扱いやすいのですが、地理情報は扱いにくいためD3.jsを使いましたが、Lefletと連携できるし、SVGベースということで、思った以上に扱いやすく感じました。