try_003_05


この記事は、「FOSS4G Advent Calendar 2016」の17日目の記事です。
今回はLeafletとCARTOを組み合わせてみようと思います。Web上に地図や地物を表示するコンテンツを手軽に公開したい場合、CARTOのサービスを利用するのはとても効果的です。

例えば
・独自UIでコンテンツを作りたい
・フロントエンド側で表示するには重すぎるデータがある
・できるだけサーバーレスで構築したい
などの場合

フロントエンド側をLeafletで、そしてバックエンド側をCARTOで実装する方法があります。

実は今年の3月に参加したハッカソンで構築したものがありまして、今回はそれを元としてFOSS4G 2016 TOKYOのハンズオンで使用した室蘭市さんのデータに置き換えてトライしてみようと思います。


まずは通常の使い方でCARTOのサービス内でMAPを作成します。表示したいデータをアップロードしてMAPに追加します。その際にレイヤ設定もしておきます。
設定が終わったら、右上にあるPUBLISHボタンを押します。
try_003_01


モーダルが開いたら、右側にあるCartoDB.jsの項目にあるJSONのURLをコピーしておきます。
try_003_02


これでバックエンド側の、CARTOの構築は終わりです。すごくシンプルです。

次に、フロントエンド側をLeafletで構築していきます。

index.html


<!--CSS関係 読み込み-->
<link rel="stylesheet" href="http://libs.cartocdn.com/cartodb.js/v3/3.15/themes/css/cartodb.css" />
<!--JS関係 読み込み-->
<script src="http://libs.cartocdn.com/cartodb.js/v3/3.15/cartodb.js"></script>

HTMLとCSSは、いつものLeafletの流れと一緒です。ただ、HTMLについては下記の記述のようにLeafletのライブラリではなくCARTO.js(CartoDB.js)を読み込みます。
Leafletの基本機能はCARTO.jsに内包されているためLeafletのライブラリは読み込む必要がありません。
※LeafletとCARTOのライブラリの両方を読み込むと競合して動かなくなると思います。


ここから本題のJS部分の説明をしていこうと思います。

script.js


//MIERUNE MONO
var mierune_mono = new L.tileLayer('https://tile.mierune.co.jp/mierune_mono/{z}/{x}/{y}.png', {
attribution: "Maptiles by <a href='http://mierune.co.jp/' target='_blank'>MIERUNE</a>, under CC BY. Data by <a href='http://osm.org/copyright' target='_blank'>OpenStreetMap</a> contributors, under ODbL."
});

//初期緯度・経度を設定
var lat = 42.3331;
var lng = 140.9800;

//地図初期表示の設定
var map = L.map('map', {
center: [lat, lng],
zoom: 13,
maxZoom: 18,
zoomControl: true,
layers: [mierune_mono]
});

//レイヤを表示(CARTO)
var sublayers = [];
var layerUrl = 'https://ユーザー名.carto.com/api/v2/viz/固有ID/viz.json';
var tsunami = cartodb.createLayer(map, layerUrl, {
https: true,
legends: true,
cartodb_logo:false,
layerIndex:1
}).addTo(map)
.done(function(layer) {
//レイヤ最前面へ
layer.setZIndex(5);
//ポイント選択時中央寄せ
layer.getSubLayer(2).on('featureClick', function(e, latlng, pos, data, subLayerIndex) {
map.panTo(latlng, {duration: 0.3});
});
//レイヤを変数に割り当て
for (var i = 0; i < layer.getSubLayerCount(); i++) {
sublayers[i] = layer.getSubLayer(i);
}
})

//スケールバー表示
L.control.scale({ imperial: false, maxWidth: 300 }).addTo(map);

//メニュー関係
//津波区域 ON/OFF
function tsunami_polygon() {
var visible = sublayers[0].isVisible();
if (visible == true) {
sublayers[0].hide();
}else if (visible == false){
sublayers[0].setSQL("SELECT * FROM tsunami_polygon");
sublayers[0].show();
}
}

//津波区域10以上表示 ON/OFF
function tsunami_polygon10() {
var visible = sublayers[0].isVisible();
if (visible == true) {
sublayers[0].hide();
}else if (visible == false){
sublayers[0].setSQL("SELECT * FROM tsunami_polygon WHERE max_ >= 10");
sublayers[0].show();

}
}

//避難経路 ON/OFF
function tsunami_line() {
var visible = sublayers[1].isVisible();
if (visible == true) {
sublayers[1].hide();
}else if (visible == false){
sublayers[1].show();
}
}

//目標地点 ON/OFF
function tsunami_point() {
var visible = sublayers[2].isVisible();
if (visible == true) {
sublayers[2].hide();
}else if (visible == false){
sublayers[2].show();
}
}


前半部分は、通常通りの背景地図読み込みや地図の初期表示設定を記述しています。


//MIERUNE MONO
var mierune_mono = new L.tileLayer('https://tile.mierune.co.jp/mierune_mono/{z}/{x}/{y}.png', {
attribution: "Maptiles by <a href='http://mierune.co.jp/' target='_blank'>MIERUNE</a>, under CC BY. Data by <a href='http://osm.org/copyright' target='_blank'>OpenStreetMap</a> contributors, under ODbL."
});

//初期緯度・経度を設定
var lat = 42.3331;
var lng = 140.9800;

//地図初期表示の設定
var map = L.map('map', {
center: [lat, lng],
zoom: 13,
maxZoom: 18,
zoomControl: true,
layers: [mierune_mono]
});


次に、CARTOのデータを読み込む部分です。
・cartodb.createLayerの引数(layerUrl変数)に、さっきコピーしたCARTOのJSONURLを記述
・.done(function(layer)〜で「ポイント選択時に画面中央寄せ」と「レイヤを各変数に割り当て」を実装


//レイヤを表示(CARTO)
var sublayers = [];
var layerUrl = 'https://ユーザー名.carto.com/api/v2/viz/固有ID/viz.json';
var tsunami = cartodb.createLayer(map, layerUrl, {
https: true,
legends: true,
cartodb_logo:false,
layerIndex:1
}).addTo(map)
.done(function(layer) {
//レイヤ最前面へ
layer.setZIndex(5);
//ポイント選択時中央寄せ
layer.getSubLayer(2).on('featureClick', function(e, latlng, pos, data, subLayerIndex) {
map.panTo(latlng, {duration: 0.3});
});
//レイヤを変数に割り当て
for (var i = 0; i < layer.getSubLayerCount(); i++) {
sublayers[i] = layer.getSubLayer(i);
}
})


ついでにスケールバーも表示しておきます。


//スケールバー表示
L.control.scale({ imperial: false, maxWidth: 300 }).addTo(map);


最後に、メニューでレイヤON/OFFを切り替える部分の実装です。
・sublayers[0].show();で指定レイヤ表示
・sublayers[0].hide();で指定レイヤ非表示

あと、試しに津波区域10m以上のみ表示する機能も実装してみました。
・sublayers[0].setSQL(SQL文);でレイヤに対してSQLの実行が可能


//メニュー関係
//津波区域 ON/OFF
function tsunami_polygon() {
var visible = sublayers[0].isVisible();
if (visible == true) {
sublayers[0].hide();
}else if (visible == false){
sublayers[0].setSQL("SELECT * FROM tsunami_polygon");
sublayers[0].show();
}
}

//津波区域10以上表示 ON/OFF
function tsunami_polygon10() {
var visible = sublayers[0].isVisible();
if (visible == true) {
sublayers[0].hide();
}else if (visible == false){
sublayers[0].setSQL("SELECT * FROM tsunami_polygon WHERE max_ >= 10");
sublayers[0].show();

}
}

//避難経路 ON/OFF
function tsunami_line() {
var visible = sublayers[1].isVisible();
if (visible == true) {
sublayers[1].hide();
}else if (visible == false){
sublayers[1].show();
}
}

//目標地点 ON/OFF
function tsunami_point() {
var visible = sublayers[2].isVisible();
if (visible == true) {
sublayers[2].hide();
}else if (visible == false){
sublayers[2].show();
}
}


完成するとこんな感じで表示されます。津波区域のポリゴンは、データが結構数細かいのですがバックエンド側でCARTOを利用しているのでサクサク描画されます。

example


独自UIで大きなデータを扱いたい場合や、できるだけサーバーレスでWebGISを実現したい場合などはLeafletとCARTOを組み合わせるのもいいかもしれません。


Leafletについて、他にも記事を書いています。よろしければぜひ。
tags - Leaflet







Q&A