JavaScript
map
mapbox
connpass
kintone
kintoneDay 23

connpassからkintoneイベントを取得して、mapboxで地図表示してみる話🗺

こんばんは!Mikeiです^^
今日はkinotne Advent Calendar 2017の23日目Postです。

今年はconnpassからkintone関連イベントを取得して、mapboxで地図表示をするというカスタマイズにチャレンジしてみました!
Google mapだと、いざ運用に乗せようとした時にライセンス問題があり、なかなか思うように使えないですが、mapboxはある程度自由に使えそうなので、要チェックです。(料金体系を見ると、地図のビュー数が月々5万を超えなければ無料枠で大丈夫)
APIやサンプルや地図のスタイルも豊富で触っていてとてもワクワクします^^

📍完成イメージ

カスタマイズ内容と完成イメージはこちらです。

  • connpassからkintoneというキーワードを含むイベントを取得して、kintoneにレコード追加
    5.png

  • kintoneに登録されている位置情報から、mapboxを使ってカスタマイズビューに地図表示

    • 今月開催のイベントのみ色付け 3.png
    • 分布図チックに改造 4.png

📍連携サービス紹介

カスタマイズの説明に移る前に、今回使った連携サービスの紹介です。

connpassとは

connpassは、イベント情報を管理するサービスのことです。kintone関連のイベントもよくこちらのサイトにアップされています。
APIリファレンスはこちらです。
スクリーンショット 2017-12-20 16.00.31.png

mapboxとは

mapboxは、Webの地図サービスです。APIやSDKなども揃っているので、カスタマイズするにも持ってこいのサービス。
サンプル集や地図のスタイルが豊富なのも嬉しいポイントです。
詳しいドキュメントはこちらになります。
スクリーンショット 2017-12-20 16.00.55.png

📍準備するもの

※githubにアップしているファイル群は右上の「Clone or download」から一括ダウンロードできます。
6.png

📍カスタマイズ手順

kintoneアプリ作成

アプリテンプレートをgithubリポジトリに用意しているので、こちらの手順に沿って読み込んでください。
「イベント管理(connpass & mapbox連携).zip」というファイルです。

また、今回地図が見やすいように2パターンとも一覧画面や詳細画面ではなく、カスタマイズビューに設置しています。
カスタマイズビューには以下のHTMLを記述しています。

カスタマイズビュー
<div id='map' style='width:100%; height:500px'></div>

※一覧名はソースコード内で指定しているので、変更すると地図が表示されなくります。

mapboxへの地図スタイル読み込み

mapboxでは下のように様々なスタイルの地図を選択してカスタマイズすることができます。(ギャラリー見るだけでも楽しいですね♪)
他の地図サービスと違うのは、テンプレートから選べるだけでなく、自分で地図スタイルを作ることもできる点です。
7.png

今回は完成イメージにもあるようにLightとDarkというテンプレートのスタイルに修正を加えたので、それを読み込んで使います。
※mapboxでは残念ながら地図上の表記を全て日本語に設定することができません。初期設定では全て英語になっているので、各国の言語で表示されるように設定し直す必要があります。今回は私の方で直したものをアップしています。(具体的には、スタイル内のテキスト設定で「name_en」となっている箇所を全て「name」に編集します。)

  1. mapboxにログイン
  2. スタイルの作成
    • 「Create a style」をクリック 8.png
    • ポップアップから「Upload a style」→「Select a file」の順にクリック
    • githubからダウンロードした「light_style.json」と「dark_style.json」をそれぞれアップロードして、「Create」ボタンをクリック
      9.png
    • 読み込みが完了したらスタイル名をクリックして一覧に戻る 10.png
    • 出来上がったスタイルの右上の「Menu」ボタンから「Share, develop & use」をクリック 11.png
    • 少し下にスクロールして出てくる「Style URL」と「Access Token」をメモ(kintoneに適用するJavaScriptファイルに転記します)
      12.png
    • 「dark_style.json」も同様に読み込み完了後、「Style URL」と「Access Token」をメモ

JavaScriptファイルの編集

  1. githubからダウンロードした「map(要編集).js」ファイルをテキストエディタで開く
  2. ソースコード最下部の「×××」4箇所を先ほどmapboxでのスタイル作成時にメモした内容に書き換えて、ファイルを保存
map(要編集).js
// イベント処理 ※書き換え必要箇所
kintone.events.on('app.record.index.show', (event) => {
    const records = event.records;
    const appId = kintone.app.getId();
    const domain = '×××'; // kintoneのサブドメイン名を記入
    const token = '×××'; // mapboxで作成したStyleのAccess tokenを記入(lightもdarkも同じ)
    const styleStandard = 'mapbox://styles/×××/×××'; // mapboxで作成したlight styleのURLを記入
    const styleChoropleth = 'mapbox://styles/×××/×××'; // mapboxで作成したdark styleのURLを記入

    // connpassからのイベント取得
    getEvents();

    // mapboxで地図表示
    if (event.viewName === '地図') {
        showMap(records, appId, domain, token, styleStandard);
    }

    // mapboxでコロプレス地図表示
    if (event.viewName === '分布地図') {
        showChoroplethMap(records, appId, domain, token, styleChoropleth);
    }
});

kintoneへの適用 & 動作確認

  1. 保存したJavaScriptファイルを作成したkintoneアプリに適用(アプリへの適用方法はこちら

    • 適用ファイルの構成が以下のようになっていることを確認して、設定を保存 13.png
  2. アプリの一覧画面に移動して、動作確認

    • 4つの一覧画面(地図表示用の2つはカスタマイズビュー) 14.png
    • 「イベント一覧(リスト)」→カレンダーアイコンをクリックして、connpassから今日以降のイベントを取得 5.png ※来年以降の予定があまり入っていないので、地図表示を楽しみたい方は、githubからダウンロードした「sample.csv」をkintoneに読み込んでから次に進んでください。
    • 「地図」→今月開催のイベントは赤印・ポップアップで詳細表示・今月のイベント数表示 15.png
    • 「分布地図」→どの地域でのイベントが多いか丸印で可視化 16.png

完成しましたでしょうか?
途中寄り道したくなる機能が沢山ありましたよね(笑)

📍妄想

mapboxはこれがきっかけで初めて触りましたが、工夫したい欲が湧いてくるツールでした^^
今回ここまでできなかったのですが、できたら実用的だなーと思います。(冬休みの課題にいかがですか^^;)

  • BIツール化(kintoneの導入社数の分布図、kintone Caféの開催マップ)
  • 旅の記録
  • バスなどの運行状況マップ

分布図系は、こんなイメージ↓
17.png
https://blog.mapbox.com/introducing-data-driven-styling-in-mapbox-gl-js-f273121143c3

18.png
https://www.mapbox.com/mapbox-gl-js/example/updating-choropleth/

📍カスタマイズのポイント解説

カスタマイズ部分が気になる方はこちらもよければご覧ください。

利用したAPI

プログラム

https://github.com/TomokoMiyake/kintone-mapbox

ポイント1:reduce関数を使う

Promiseの順番処理を行うためにreduce関数を使います。

map(要編集).js
// kintoneへ登録用のデータ配列作成
let post = [];
return body.reduce((promise, formatedData) => {
    return promise.then(() => {
        const getRecords = {
            'app': kintone.app.getId(),
            'query': 'url =' + '"' + formatedData.event_url + '"'
        };
        return kintone.api(kintone.api.url('/k/v1/records', true), 'GET', getRecords).then((getResp) => {
            if (getResp.records.length === 0) {
                const records = {
                    'title': {
                        'value': formatedData.title
                    },
                    'start': {
                        'value': formatedData.started_at
                    },
                    'end': {
                        'value': formatedData.ended_at
                    },
                    'url': {
                        'value': formatedData.event_url
                    },
                    'address': {
                        'value': formatedData.address
                    },
                    'place': {
                        'value': formatedData.place
                    },
                    'lat': {
                        'value': formatedData.lat
                    },
                    'lon': {
                        'value': formatedData.lon
                    }
                };
                post.push(records);
                return post;
            }
        });
    });
}, Promise.resolve()).then(() => {
    ////// 省略 //////
});

ポイント2:地図データのマッピング

中心の座標の指定やkitnoneアプリ内の座標データを地図データにプロットします。

map(要編集).js
const map = new mapboxgl.Map({
    container: 'map',
    style: styleChoropleth,
    center: [137.6850225, 38.258595],
    zoom: 6
});

// 地図にピン打ちするデータ作成
const features = records.map((record) => {
    const link = 'https://' + domain + '.cybozu.com/k/' + appId + '/show#record=' + record.$id.value;

    const feature = {
        type: 'Feature',
        geometry: {
            type: 'Point',
            coordinates: [parseFloat(record.lon.value), parseFloat(record.lat.value)]
        },
        properties: {
            title: '<a href=' + link + '>' + record.title.value + '</a>',
            description: record.address.value
        }
    };
    return feature;
});

ポイント3:マーカー(地図上のアイコン)のプロット

「地図」では条件付きで赤色と黒色のマーカーを分けているので、それ用に2つの要素を作成します。
それぞれのマーカーには緯度経度のデータやポップアップで表示する内容を指定します。

map(要編集).js
// マーカーを地図に追加
geojson.features.forEach(function(marker) {
    if (isNaN(marker.geometry.coordinates[0]) || isNaN(marker.geometry.coordinates[1])) {
        return;
    }

    // HTML要素の作成
    const elThisMonth = document.createElement('div');
    const el = document.createElement('div');
    if (marker.properties.icon === 'thisMonth') {
        el.className = 'marker-this-month';
    } else if (marker.properties.icon === 'notThisMonth') {
        el.className = 'marker';
    }
    // マーカー要素を作成
    new mapboxgl.Marker(elThisMonth)
        .setLngLat(marker.geometry.coordinates)
        .setPopup(new mapboxgl.Popup({ offset: 25 })
        .setHTML('<h3><b>' + marker.properties.title + '</b></h3><p>' + marker.properties.description + '</p>'))
        .addTo(map);

    new mapboxgl.Marker(el)
        .setLngLat(marker.geometry.coordinates)
        .setPopup(new mapboxgl.Popup({ offset: 25 })
        .setHTML('<h3><b>' + marker.properties.title + '</b></h3><p>' + marker.properties.description + '</p>'))
        .addTo(map);
});

JavaScriptで生成したクラスにCSS側でFontAwesomeをアイコンとしてあてます。

map.css
.marker:before {
    font-family: FontAwesome;
    content: '\f041';
    color: #585858;
    font-size: 2em;
    text-shadow: 0px 0px 1px #FFFFFF;
}

📍おわりに

最後まで読んでくださりありがとうございました!間違ってるところがあればご指摘いただけると嬉しいですm(__)m
では皆さん、Merry Christmas and Happy New Year!!!🎄