Google Maps PlatformでkintoneのSFAアプリに地図を表示して顧客訪問を効率化する

新緑がきれいな季節ですね🍃
テクノロジー部の森です。今回は、営業のお仕事として重要でありながら時間のかかる「顧客訪問」の効率化に関するkintone実装のお話です。

この記事にたどり着いた方は、kintoneを営業管理システム:SFA(Sales Force Automation)として使っている方かもしれません。kintoneで作ったSFAであれば、たいていの場合、住所のテキスト情報があると思います。今回はこれをkintone内で地図にプロットする方法をご紹介します。

Google Maps Platformについて

今回、地図関連の機能についてはGoogle Maps Platformを利用します。Google Maps Platformは、Googleが提供する地理空間データプラットフォームです。よく見る地図にピンをプロットするMaps APIだけでなく、Googleマップで使える最短経路を計算するRoutes API、住所を緯度経度に変換するGeocoding APIなども提供しています。

今回kintoneに登録されている住所テキストを緯度経度に変換するのにGeocoding API、緯度経度からマーカーで可視化するのにMaps APIを使います。

Google Maps PlatformはGoogle Cloud Platform(GCP)の1サービスなので、GCPにてプロジェクトを作成、APIを有効化してAPIキーを取得する必要があります。

この手順については以下記事が詳しいです。記事を参考にAPIキーをご準備ください。

【Qiita – Google Maps API を使ってみた】
https://qiita.com/Haruka-Ogawa/items/997401a2edcd20e61037

記事ではMaps JavaScript APIを有効化していますが、加えてGeocoding APIも有効にしてください。

kintoneアプリの準備

SFAアプリのサンプルとして、以下フィールドを持つアプリを用意しました。すでにSFAアプリがある場合は緯度、経度のフィールドだけ追加するような形になると思います。コロンの後ろはフィールド名です。

  • 顧客名:name(文字列(1行))
  • 都道府県:prefecture(ドロップダウン)
  • 市区町村:city(文字列(1行))
  • 住所1:address1(文字列(1行))
  • 住所2:address2(文字列(1行))
  • 緯度:lat(数値)
  • 経度:lng(数値)

※lat = latitudeの意味。lng = longtudeの意味。

住所関係のフィールドはこの通りでなくてもOKです。最終的には1つの文字列に結合してGeocoding APIに渡すので、都道府県が「文字列(1文字)」フィールドであったり、住所1,2がまとめられていても問題ありません。

さらに、地図表示用に一覧画面をカスタムで用意します。

設定 > 一覧で新規一覧を作成。レコード一覧の表示形式は「カスタマイズ」を選び、以下HTMLタグを入力して保存して下さい。「ページネーションを表示する」は不要です。

<div id="map"></div>

Geocodingによる緯度経度の取得

まずは具体的な実装に入る前に、Google Maps API, Geocoding APIが使えるようにライブラリの読み込みを行いましょう。

(g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})
({key: "<ここにAPIキー>"});

GCPの画面で取得したAPIキーを<ここにAPIキー>の箇所に入れてください。これをコードの冒頭に呼び出せばコード内でAPIを使うことができます。

次にGeocodingのAPIを使って住所を緯度・経度に変換する機能を実装します。今回、変換処理はレコードの保存成功時に行われるようにします。早速コードです。

//1.新規作成、更新、一覧編集が成功した際に地図情報を追加して保存する
kintone.events.on(["app.record.create.submit.success", "app.record.edit.submit.success", "app.record.index.edit.submit.success"], async (event) => {
    const location = await getLocation(event.record);
    if (location != undefined) {
        const update = {
            lat: { value: location.lat() },
            lng: { value: location.lng() },
        };
        const params = { app: event.appId, id: event.recordId, record: update };
        await kintone.api(kintone.api.url("/k/v1/record", true), "PUT", params);
    }

    return event;
});

//2.GoogleMapsAPIを呼び出し、緯度経度情報を返す
async function getLocation(record) {
    const addressValue = getAddress(record);
    if (!addressValue) {
        return;
    }
    const { Geocoder } = await google.maps.importLibrary("geocoding");
    const gc = new Geocoder();
    return new Promise((resolve, reject) => {
        gc.geocode(
            {
                address: addressValue,
                language: "ja",
                country: "JP",
            },
            (results, status) => {
                if (status === google.maps.GeocoderStatus.OK) {
                    resolve(results[0].geometry.location);
                } else {
                    reject();
                }
            }
        );
    });
}

//3.レコードから住所情報を作る
function getAddress(record) {
    const pref = record["prefecture"]["value"];
    if (pref.length === 0) {
        return null;
    }
    const city = record["city"]["value"];
    if (city.length === 0) {
        return null;
    }
    const address1 = record["address1"]["value"];
    const address2 = record["address2"]["value"];
    return `${pref}${city}${address1}${address2}`;
}

1の処理は通常のkintoneのイベント処理です。今回はレコードが問題なく保存できた場合に緯度経度計算に入るようにしたかったので、*.submit.successイベントを使っています。

2の処理でGeocoding APIを呼んでいます。先のライブラリ読み込みはGoogle Maps APIのコアの部分だけを読み込むので、importLibrary(‘geocoding’)を使ってGeocoding APIに関する処理を追加で読み込み、実行します。

3の処理で2で使う住所の文字列を作っています。都道府県(prefecture)や市区町村(city)までの場合はnullにしています。Geocodingは都道府県や都道府県+市区町村でも緯度経度を返してくれますが、この場合、その地域の中心座標(多分)を返すようです。地図参照時にミスリードになるので、詳細な住所情報が入っている場合のみ緯度経度を計算するようにしています。

住所を登録して保存すると以下のように緯度、経度が記入されます。

本番では、API呼び出し回数を減らすために住所に変更が無い場合はAPIを呼ばないような制御もあるといいです。

地図へのマッピング

地図はkintoneアプリの準備で作った「地図」のカスタム一覧画面を使います。早速コードです。

// 4.地図ビューの場合に地図を表示する
kintone.events.on(["app.record.index.show"], async function (event) {
    const viewName = event.viewName;
    if (viewName == "地図") {
        await showCustomIndex(event);
    }
    return event;
});

// 5.レコードを取得してマーカー処理を行う
async function showCustomIndex(event) {
    const params = { app: event.appId };
    const resp = await kintone.api(kintone.api.url("/k/v1/records", true), "GET", params);
    await setLocation_address(resp.records);
    return event;
}

// 6.レコードの緯度経度情報からマーカーを表示する
async function setLocation_address(records) {
    const { Map } = await google.maps.importLibrary("maps");
    const { Marker } = await google.maps.importLibrary("marker");

    const lat = [];
    const lng = [];
    const recno = [];

    for (let i = 0; i < records.length; i++) {
        if (records[i].lat.value && records[i].lng.value) {
            lat.push(records[i].lat.value);
            lng.push(records[i].lng.value);
            recno.push(records[i].$id.value);
        }
    }

    // 地図を表示する div 要素を作成
    const mapEl_address = document.createElement("div");
    mapEl_address.setAttribute("id", "map_address");
    mapEl_address.setAttribute("name", "map_address");
    mapEl_address.setAttribute("style", "width: auto; height: 500px;");
    const elMap = document.getElementById("map");
    elMap.appendChild(mapEl_address);

    const center = new google.maps.LatLng(36.0, 138.0);

    var opts = {
        zoom: 10,
        center: center,
        mapTypeId: google.maps.MapTypeId.ROADMAP,
        scaleControl: true,
        titie: "map",
    };

    var map = new Map(document.getElementById("map_address"), opts);

    google.maps.event.addListenerOnce(map, "idle", function () {
        const marker = [];
        const m_latlng = [];

        for (let i = 0; i < lat.length; i += 1) {
            if (isNaN(lat[i]) === false && isNaN(lng[i]) === false) {
                m_latlng[i] = new google.maps.LatLng(lat[i], lng[i]);
                marker[i] = new Marker({
                    position: m_latlng[i],
                    map: map,
                    label: recno[i].toString(),
                });
            }
        }
    });
}

4の処理で一覧表示画面のうち、「地図」というビュー名の場合のみに処理が行われるようにしています。

5の処理ではアプリのレコードをすべて取得して6の地図処理に渡しています。顧客データの状態によって(例えば、アポ予定フラグがある顧客などに)絞り込む場合はparamsの条件を指定してください。

6の処理は、受け取ったレコードの座標とレコードIDからマーカーを生成してプロットしています。途中centerは自社住所の緯度経度など初期位置として便利な値に変えてください。

実際に地図表示させた画面は以下のようになります。

まとめ

今回はkintoneのSFAアプリで住所情報から座標を割り出して、それをGoogleマップ上にプロットする方法を紹介しました。

弊社で実際に実装した際には、さらに改良を加え、出発地である自社と訪問予定の顧客住所を加味した「近隣」に表示を絞りました。こうすることで、訪問予定以外の近隣顧客にもついでに訪問できるようになり、顧客訪問の効率化を図ることができました。

今回の記事が、皆さんの業務効率化に役立ったらうれしいです。

GLASSではkintoneなどのシステム開発のご依頼も承っています。ご相談は無料ですので、お気軽にお問い合わせ下さい。

マーケティングの悩みを無料相談する
カテゴリー: DXシステム開発
GLASSで一緒に働いてみませんか?