GoogleMapsAPIとTwitterAPIの初心者が「つぶやきマップ」を作った

#はじめに
自分がいつどこで何をつぶやいたかが分かれば、ライフログみたいで楽しそうだなと思ったので「つぶやきマップ」作ってみました。

GoogleMapsAPIもTwtterAPIも触るんは初めてです。
いろんなサイトにお世話になりました。。。。


![スクリーンショット 2019-11-25 14.14.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/445408/d4ca8dcd-e24a-ca6e-b09c-df19ed888805.png)
⬆️これはしまなみ海道を自転車で走った時
（どこで何をしてたか分かって楽しい！バチクソ個人情報だ！）

#0.開発環境
PC:　MacBook Air (Retina, 13-inch, 2018)
OS:　macOS Mojave (ver 10.14.1)
プロセッサ:　1.6 GHz Intel Core i5
メモリ:　16 GB 2133 MHz LPDDR3
エディタ:　Visual Studio Code

#1.前準備
今回は移動履歴をGoogleMapのロケーション履歴からダウンロードして使用します。
また、ツイート内容はTwitterAPIを使用して取得します。
それぞれ、データのダウンロードと登録が必要です。

##ロケーション履歴の取得
ロケーション履歴の取得方法は、こちらを参考にしました。
[グーグルマップの履歴(timeline)をダウンロード](https://qiita.com/maatumoDev/items/b2a97006bb382c3ed649)

 [Google Maps の行動ログを抽出する](https://quzee.hatenablog.com/entry/2017/11/19/235725)


##GoogleMapsAPIの登録方法
GoogleMaspAPIの使用時に必要な登録方法は、こちらを参考にしました。
[Google Maps API を使ってみた](https://qiita.com/Haruka-Ogawa/items/997401a2edcd20e61037)

##TwitterAPIの登録方法
TwitterAPIの登録方法は、こちらを参考にしました。
[Twitter API 登録 (アカウント申請方法) から承認されるまでの手順まとめ](https://qiita.com/kngsym2018/items/2524d21455aac111cdee)


##Tweetデータ準備
ロケーション履歴のデータがjson形式で、ファイル形式を合わせると便利そうなので、tweetデータもjson形式（tweet_timeline.json)で準備します。

（今回はtweepyというライブラリを使用したかったので、pythonで書いてます。)

```get_tweet_data.py
import tweepy
import json
import time
import calendar


# Twitter APIを使用するためのConsumerキー、アクセストークン設定
Consumer_key = '<TwitterAPI登録申請して取得した API key>'
Consumer_secret = '<TwitterAPI登録申請して取得したConsumer_secret>'
Access_token = '<TwitterAPI登録申請して取得したAccess_token>'
Access_secret = '<TwitterAPI登録申請して取得したAccess_secret>'

# 認証
auth = tweepy.OAuthHandler(Consumer_key, Consumer_secret)
auth.set_access_token(Access_token, Access_secret)

api = tweepy.API(auth, wait_on_rate_limit=True)


# ツイートデータ配列
tweet_data = []

# 取得先アカウント アカウント名の@を除いた名前を記入
user = "＊＊＊＊＊＊"

# jsonデータ作成
for tweet in tweepy.Cursor(api.user_timeline, screen_name=user, exclude_replies=True, include_entities=True).items():

    # twitterAPIのつぶやき時間をUNIXに変換
    tweet_utc_time = time.strptime(
        str(tweet.created_at), "%Y-%m-%d %H:%M:%S")
    tweet_unix_time = calendar.timegm(tweet_utc_time)

    # 画像があれば画像のURL要素追加
    if 'media' in tweet.entities:
        for media in tweet.entities['media']:
            tweet_image_url = media['media_url_https']

            # ツイートデータ配列に画像要素追加
            tweet_data.append({
                "id": tweet.id, "created_at": tweet_unix_time, "text": tweet.text,
                "fav": tweet.favorite_count, "RT": tweet.retweet_count, "image": tweet_image_url
            })

    else:
        # ツイートデータ配列に要素追加
        tweet_data.append({
            "id": tweet.id, "created_at": tweet_unix_time, "text": tweet.text,
            "fav": tweet.favorite_count, "RT": tweet.retweet_count
        })

tweet_dict = {"timeline": tweet_data}

# jsonファイル名
fw = open('tweet_timeline.json', 'w')
# jsonファイル出力　json.dump関数でファイルに書き込む
json.dump(tweet_dict, fw, indent=4, ensure_ascii=False)

```


TwitterAPIでは特殊な形式で時間が取れるので、ロケーション履歴と合わせるようにUnix時間に変換し、画像データも扱いやすいフォーマットでjsonファイルにまとめています。

#2.GoogleMapにTweetをマッピング
あとは、準備したロケーション履歴とTweetデータを使って、座標とTweetデータのマッピングをしていけば完成です。
GoogleMapsAPIを使うため、javascript,html,cssを利用します。

こちらのサイトにかなりお世話になりました。。。ほぼコピペレベルです。。
[Google Maps APIでjsonのデータからマーカーを設置する](http://cly7796.net/wp/javascript/add-markers-from-json-data-with-the-google-maps-api/)


```sample.js
const START_DAY = '2019/4/7';
const END_DAY = '2019/4/8';
const POINTS = 1;

var date;
var ready = { api: false, ajax: false };
var map;
var mapData;
var mapOptions = {
    center: { // 地図の緯度経度
        lat: 35.700000,
        lng: 139.772000
    },
    zoom: 17, // 地図の拡大率
    scrollwheel: false, // ホイール操作で拡大縮小させるかどうか
    mapTypeControl: false, // マップ切り替えのコントロールを表示するかどうか
    streetViewControl: false // ストリートビューのコントロールを表示するかどうか
}
var latlngs = [];
var tweetdata;

var infoWindowIndex = [];

var startDay = new Date(START_DAY);
var unixTimestampstartDay = Math.round(startDay.getTime());
console.log(unixTimestampstartDay)

var endDay = new Date(END_DAY);
var unixTimestampendDay = Math.round(endDay.getTime())
console.log(unixTimestampendDay)

var aveLat = 0;
var aveLng = 0;


$(function () {
    $.ajax({
        url: 'tweet_timeline.json',
        dataType: 'json',
        cache: false
    })
        .then(
            function (data) {
                console.log('Twitterのタイムライン取得に成功しました。');
                tweetData = data["timeline"];
                ready['ajax'] = true;

            },
            function () {
                console.log('Twitterのタイムライン取得に失敗しました。');
            }
        );
});

$(function () {
    $.ajax({
        url: 'ロケーション履歴.json',
        dataType: 'json',
        cache: false
    })
        .then(
            function (data) {
                console.log('位置情報の取得に成功しました。');
                mapData = data["locations"];
                ready['ajax'] = true;

                generate_map();
            },
            function () {
                console.log('位置情報の取得に失敗しました。');
            }
        );
});


/**
 * Google Maps APIの準備完了後の処理
 */
function api_ready() {
    ready['api'] = true;
    generate_map();
}

/**
 * 地図を生成する
 */
function generate_map() {
    if (ready['api'] && ready['ajax']) {
        map = new google.maps.Map(document.getElementById('map'), {
            // ズームレベル
            zoom: 10,
            // 中心点緯度経度
            center: new google.maps.LatLng(mapData[0].latitudeE7 * 0.0000001, mapData[0].longitudeE7 * 0.0000001),
            // 距離目盛りの表示
            scaleControl: true,
            // 地図の種類
            mapTypeId: google.maps.MapTypeId.ROADMAP

        });
        add_marker();
        generate_polyline();
    }
}

/**
 * 地図にマーカーを追加する
 */
function add_marker() {
    var markerNum = 0;
    var j = 0;

    var infoWindowNum = 0;
    for (var i = 0; i < mapData.length; i += POINTS) {
        var item = mapData[i];
        if (startDay <= item.timestampMs && item.timestampMs <= endDay) {

            date = new Date(Number(item.timestampMs));
            latlngs.push(new google.maps.LatLng(item.latitudeE7 * 0.0000001, item.longitudeE7 * 0.0000001));

            // 吹き出しの生成
            var ins = '<div class="map-window">';
            ins += '<font size="2">'
            //ins += '<p class="map-window_name">' + date + '</p>';
            var flag = 0;
            var tw = 0;
            var itemNext = mapData[i + POINTS];

            // 吹き出しにTweet内容を反映
            for (var j = 0; j < tweetData.length; j++) {
                if (item.timestampMs <= (tweetData[j].created_at * 1000) && (tweetData[j].created_at * 1000) < itemNext.timestampMs) {
                    console.log(date);
                    console.log(tweetData[j].text);
                    ins += '<p class="map-window_name">' + tweetData[j].text + '</p>';
                    // 連想配列にキー"image"があれば画像追加処理をする
                    if (tweetData[j].image) {
                        ins += "<img src='" + tweetData[j].image + ":thumb'></br >";
                    }
                    tw = 1;
                }
            }
            ins += '</font>';
            ins += '</div>';

            //Tweetのあるマーカーだけ吹き出し生成
            if (tw == 1) {
                // マーカーの設置
                var marker = new google.maps.Marker({
                    position: latlngs[markerNum],
                    map: map,
                    animation: google.maps.Animation.DROP,
                });

                var infoWindow = new google.maps.InfoWindow({
                    content: ins
                });

                infoWindowIndex[infoWindowNum] = i;
                infoWindowNum++;

                // マーカーのイベント設定
                add_event_to_marker(marker, infoWindow, i);
            }

            aveLat += (item.latitudeE7 * 0.0000001);
            aveLng += (item.longitudeE7 * 0.0000001)
            markerNum++;
        }

    }
    // 座標の平均を地図の中心に設定する
    aveLat /= markerNum;
    aveLng /= markerNum;
    map.setCenter(new google.maps.LatLng(aveLat, aveLng));

}

/**
 * マーカーにイベントを追加する
 * @param {object} marker     (required) マーカーの情報
 * @param {object} infoWindow (required) 吹き出しの情報
 * @param {number} index      (required) 地図情報のインデックス番号
 */
function add_event_to_marker(marker, infoWindow, index) {
    var item = mapData[index];
    item['marker'] = marker;
    item['infoWindow'] = infoWindow;

    item['infoWindow'].open(map, item['marker']);

    // マーカークリック時に吹き出しを表示する
    item['marker'].addListener('click', function (e) {
        infoWindows_hide();
        item['infoWindow'].open(map, item['marker']);
    });
}

/**
 * 吹き出しを非表示にする
 */
function infoWindows_hide() {
    for (var i = 0; i < infoWindowIndex.length; i++) {
        if (startDay <= mapData[infoWindowIndex[i]]['timestampMs'] && mapData[infoWindowIndex[i]]['timestampMs'] <= endDay)
            mapData[infoWindowIndex[i]]['infoWindow'].close();
    }
}

/**吹き出しを順番に表示する */
function infoWindows_open() {
    var j = 0;
    for (var i = 0; i < mapData.length; i++) {
        if (infoWindowIndex[j] == i) {
            mapData[i]['infoWindow'].open(map, mapData[i]['marker']);
            j++;
        }
    }
}


/**ポリラインを生成する */
function generate_polyline() {
    // 線を引く
    lines = new google.maps.Polyline({
        path: latlngs,
        strokeColor: "#8a2be2",
        strokeOpacity: .7,
        strokeWeight: 5
    });
    lines.setMap(map);
}

```


```map.html
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8" />

    <title>つぶやきマップ</title>

    <link rel="stylesheet" href="sample.css" />

</head>

<body>

    <div id="map"></div>
    <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
    <script src="./sample.js"></script>
    <script
        src="https://maps.googleapis.com/maps/api/js?key=<用意したGCPのkey>&callback=api_ready"></script>

</body>

</html>
```

```sample.css
#map {
    width: 1000px;
    height: 600px;
}
```

タラタラとコードかいてますが、やっていることは単純で、ロケーション履歴の各地点での時間とツイート時間と比較して近い時間のツイートを表示するようにしています。
具体的には、座標はだいたい3分ごとと細かく残っていたので、ロケーション履歴のある時間と次の時間（約3分後）の時間内につぶやいたTweetを表示させています。

また、移動した軌跡がわかるようにpolylineというGoogleMapのメソッドを使って軌跡も表示しています。

#3.注意点
①位置情報、Tweet内容、APIのキー情報は個人情報なので絶対に公開しないこと！

②ローカル環境で実行する場合、chromeでAjaxでローカルファイルにアクセスするとエラーが出ます。sudoでChormeを実行しましょう！
[[*その他*] ChromeにてAjaxでローカルファイルにアクセス](https://qiita.com/cigalecigales/items/33afaa42f91542ffa62e)

③ロケーション履歴とTweetデータが取れる日付までしか遡れない！（当たり前）

#4.参考にしたWeb記事

参考というかほぼコピペかも知れません。。。。すいません。。。本当ありがとうございます。
（いろんなサイト参考にしたので、もし「あれ、このコード俺が書いたやつのコピペじゃね？」とかあれば教えてください。。。）


・[旅行や外出などの記録に Google タイムラインを使う](https://www.iphone-mysterious.com/20190929124638.html)

・[【10日目の捕捉】Twitter APIのcreated_atをUNIX時間に変換してDATETIMEに保存する](https://dailytextmining.hatenablog.com/entry/2018/07/03/022622)

・[twitter APIで遊んでみる #3(検索結果の取得)](https://qiita.com/tomozo6/items/d7fac0f942f3c4c66daf)

・[ポリラインの表示 Google Maps API V3 JavaScript の使い方](https://www.google-mapi.com/googlemaps/polyline-display.html)

#その他
作って満足するタイプなので説明が抜けていることろあるかも知れないですが、わかりづらいところあれば聞いてください。。。