D3.jsを使ってCO2排出量を可視化してみる。

NASA EARTHのSizing Up the Carbon Footprint of Citiesがかっこよかったので、D3.jsの勉強を兼ねて、環境省CO2排出量の現況推計のデータから、CO2排出量の可視化画像を作ってみました。

■成果物:日本のCO2排出量可視化画像(2015年度) f:id:termat:20190415020145p:plain

市町村の境界データの作成(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 FrameworkCSVファイルを読み込んだ後、「市町村コード」と「描画色」の連想配列を作成し、geojson.propertiesの「市町村コード(N03_007)」から各市町村ポリゴンの描画色を呼び出しています。

f:id:termat:20190415022511p:plain

$(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: '&copy; '+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の傾斜角陰影図と合成(差分)し、それっぽく調整しました。 f:id:termat:20190415025340p:plain f:id:termat:20190415020145p:plain

雑感

これまで、情報可視化にはProcessing.jsを使用していました。
Processing.jsJavaライクでとても扱いやすいのですが、地理情報は扱いにくいためD3.jsを使いましたが、Lefletと連携できるし、SVGベースということで、思った以上に扱いやすく感じました。