現在位置を取得し、近くのランドマークを探す(wordpress版)

以前のエントリ「現在位置を取得し、近くのランドマークを探す」のwordpress版です。
カスタム投稿(landmark)を作成し、その情報をもとにWP REST APIで取得し描画していきます。
今回はプラグインのAdvanced Custom Fields(以下ACF)を使用します。
GoogleMapsのAPIも使用していきます。(比較したい地点の経度と緯度がわかっていればGoogleMapsのAPIを使わなくても大丈夫)

カスタム投稿をREST APIで取得できる様にする

show_in_restrest_baseにAPI周りのことを記載すると/wp-json/wp/v2/landmark/でカスタム投稿(landmark)を取得することができます。

function create_post_type()
{
	$supports = [
		'title',  // title
		'editor',  // editor
	];
	register_post_type(
		'landmark',  //  カスタム投稿
		array(
			'label' => 'ランドマーク',
			'public' => true,
			'has_archive' => false,
			'menu_position' => 7,
			'supports' => $supports,
			'show_in_rest' => true, //rest api での取得を有効にする
			'rest_base' => 'landmark' // rest api での取得名  /wp-json/wp/v2/landmark/
		)
	);
}
add_action('init', 'create_post_type');

ACFのGoogleマップを使う

ACFには多くのものが備わっていますが、今回はGoogleマップを使って楽に位置情報を入力していきます。

ACF | Google Map
https://www.advancedcustomfields.com/resources/google-map/

ACFでGoogleMapを選択します。

カスタム投稿記事へ移動すると、「このページでは Google マップが正しく読み込まれませんでした。」とポップアップが出てくるのでGoogleMapのAPIを設定していきます。

APIキーの設定

Google Cloud プラットフォームへ行き、新しいプロジェクトを作成します。
有効にするAPIは下記3つ。

  • Geocoding API
  • Maps JavaScript API
  • Places API

左メニューの「APIとサービス」>「認証情報」をクリックし、上部の「CREATE CREDENTIALS」ボタンをクリックAPIキーを発行します。

APIキーが発行されたら、キーをコピーしておきます。

発行したAPIキーはそのままではいけないので制限をかけておきます。発行したAPIキー名をクリックしてキーの制限を行います。

アプリケーションの制限を「HTTPリファラー(ウェブサイト)」にし、「ウェブサイトの制限」に任意のURLを設定します。

functions.phpに記述追加

functions.phpにGoogleMapを使える様にする記述を追加します。

function my_setup()
{
    global $global_api_key;
    $global_api_key = '先ほど取得したAPIキー';
}
add_action('after_setup_theme', 'my_setup');

function my_acf_google_map_api($api)
{
    global $global_api_key;
    $api['key'] = $global_api_key;
    return $api;
}
add_filter('acf/fields/google_map/api', 'my_acf_google_map_api');

無事に管理画面でGoogleMapが読み込まれました。

カスタム投稿のカスタムフィールドをREST APIの項目に含める

デフォルトだとREST APIにはカスタム投稿の値は含まれていないため、functions.phpに追記していきます。

//wp rest api カスタマイズ
add_action('rest_api_init', 'api_add_fields');
function api_add_fields()
{
    register_rest_field(
        'landmark', //カスタム投稿名
        'googlemap', //ACF カスタムフィールド名
        array(
            'get_callback'    => 'register_fields',
            'update_callback' => null,
            'schema'          => null,
        )
    );
}
function register_fields($post, $name)
{
    return get_post_meta($post['id'], $name, true);
}

WP REST APIで呼び出す

jsについては以前のエントリ「現在位置を取得し、近くのランドマークを探す」の方も併せてご覧ください。

htmlとjs

<div class="show-landmark-button-area">
    <button type="button" id="show-landmark-button">近くの東京ランドマーク</button>
</div>
<div id="show-landmark-area">
    <p>現在位置から50,000m以内のランドマーク</p>
    <ul id="show-landmark">

    </ul>
</div>

<div id="loading-area"></div>
    // ヒュベニの公式
    const hubeny = (lat1, lng1, lat2, lng2) => {
        function rad(deg) {
            return deg * Math.PI / 180;
        }
        // 緯度経度をラジアンに変換
        lat1 = rad(lat1);
        lng1 = rad(lng1);
        lat2 = rad(lat2);
        lng2 = rad(lng2);

        // 緯度差
        var latDiff = lat1 - lat2;
        // 経度差算
        var lngDiff = lng1 - lng2;
        // 平均緯度
        var latAvg = (lat1 + lat2) / 2.0;
        // 赤道半径
        var a = 6378137.0;
        // 極半径
        var b = 6356752.314140356;
        // 第一離心率^2
        var e2 = 0.00669438002301188;
        // 赤道上の子午線曲率半径
        var a1e2 = 6335439.32708317;

        var sinLat = Math.sin(latAvg);
        var W2 = 1.0 - e2 * (sinLat * sinLat);

        // 子午線曲率半径M
        var M = a1e2 / (Math.sqrt(W2) * W2);
        // 卯酉線曲率半径
        var N = a / Math.sqrt(W2);

        t1 = M * latDiff;
        t2 = N * Math.cos(latAvg) * lngDiff;
        return Math.sqrt((t1 * t1) + (t2 * t2));
    }

    //ローディング
    const loading = (status) => {
        const loadingArea = document.getElementById('loading-area');
        if (status === 'show') {
            loadingArea.classList.add('is-show');
        }
        if (status === 'hidden') {
            loadingArea.classList.remove('is-show');
        }
    }

    //距離整形
    const dataShaping = (distance) => {
        let restult = Math.round(distance);
        restult = Number(restult).toLocaleString();
        return restult;
    }

    //リスト出力
    const resultList = (array) => {
        const showLandmark = document.getElementById('show-landmark');
        let resultContent = '';

        if (array.length < 0) {
            resultContent = '<li class="empty">近くにありません。</li>';
        } else {
            for (let i = 0; i < array.length; i++) {
                resultContent = resultContent + `<li><div><p>${array[i]['title']}</p><p>現在位置との距離 : 約${dataShaping(array[i]['result'])}m</p></div></li>`;
            }
        }

        showLandmark.innerHTML = resultContent;
        loading('hidden');

    }

    const NUM = 50000;
    const getSuccess = (pos) => {

        //現在地の緯度経度
        let lat1 = pos.coords.latitude;
        let lng1 = pos.coords.longitude;
        let resultArray = [];

        // REST API取得
        fetch("https://xxxx.xx/wp-json/wp/v2/landmark/")
            .then(response => {
                if (response.ok) {
                    return response.json();
                } else {
                    return Promise.reject(new Error('エラーです'));
                }
            })
            .then(data => {
                // console.log(data);

                for (let i = 0; i < data.length; i++) {
                    lat2 = data[i].googlemap.lat; //軽度
                    lng2 = data[i].googlemap.lng; //緯度

                    let result = hubeny(lat1, lng1, lat2, lng2);

                    if (result < NUM) {
                        let resultData = [];
                        // console.log(data[i]);
                        resultData['title'] = data[i].title.rendered;
                        resultData['result'] = result;
                        resultData['googlemap'] = data[i].googlemap;

                        resultArray.push(resultData);
                    }
                }
                // console.log(resultArray);
                resultList(resultArray);
            });
    };



    //エラー
    const geoError = () => {
        var pos = {
            'coords': {
                'latitude': 0,
                'longitude': 0
            }
        };
        getSuccess(pos);
        loading('hidden');
        console.log('取得失敗');
    };

    // GeoLocationAPIで現在地の座標を取得する
    const showLandmarkButton = document.getElementById("show-landmark-button");
    showLandmarkButton.addEventListener('click', () => {
        loading('show');
        navigator.geolocation.getCurrentPosition(getSuccess, geoError, {
            enableHighAccuracy: true
        });
    })

REST APIを取得する

https://xxxx.xx/wp-json/wp/v2/landmark/ で、あらかじめ設定しておいたカスタム投稿(landmark)を取得できます。

fetch("https://xxxx.xx/wp-json/wp/v2/landmark/")
    .then(response => {
      if (response.ok) {
        return response.json();
      } else {
        return Promise.reject(new Error('エラーです'));
      }
    })
    .then(data => {
      console.log(data);
    });

取得したデータをconsole.logなどで見てみると下記の様に。
このデータのうちgooglemapの項目は、取得できる様にfunctions.phpに記載したカスタムフールドです。
ACFのGoogleMapを取得すると、中に配列があり、経度緯度等の情報が入っています。この経度緯度を使って現在位置との2地点距離を比較します。

    content: {
        rendered: '',
        protected: false
    }
    date: "2022-08-16T09:30:27"
    date_gmt: "2022-08-16T09:30:27"
    googlemap: {//カスタムフィールド
        address: '日本、東京都渋谷区道玄坂1丁目12−1 渋谷マークシティ',
        lat: 35.6581085,
        lng: 139.6986263,
        zoom: 14,
        place_id: 'ChIJQcMHZ6qMGGARWHRxn-Vn8uA',
        …
    }
    guid: {
        rendered: 'https://xxxx.xx/?post_type=landmark&p=43'
    }
    id: 43
    link: "https://xxxx.xx/landmark/%e6%b8%8b%e8%b0%b7%e3%83%9e%e3%83%bc%e3%82%af%e3%82%b7%e3%83%86%e3%82%a3/"
    modified: "2022-08-16T09:31:18"
    modified_gmt: "2022-08-16T09:31:18"
    slug: "%e6%b8%8b%e8%b0%b7%e3%83%9e%e3%83%bc%e3%82%af%e3%82%b7%e3%83%86%e3%82%a3"
    status: "publish"
    template: ""
    title: {
        rendered: '渋谷マークシティ'
    }
    type: "landmark"
    _links: {
            self: Array(1),
            collection: Array(1),
            about: Array(1),
            wp: attachment: Array(1),
            curies: Array(1)
        }

【補足】PHPでACFのGoogleMapの経度緯度を取得する場合

$googlemap =  get_field('googlemap');
//ACFのgooglemapは取得すると配列になっている
// var_dump($googlemap);

//経度緯度
echo $googlemap['lat'];
echo $googlemap['lng'];

ACFのGoogleMapを必ずしも使う必要はない

今回はACFのGoogleMapを使って管理画面からGoogleMapを使って手軽に地点を設定できる様にしましたが、その地点の経度緯度がわかれば計算ができるので、GoogleMapを使わず、経度と緯度を入力するカスタムフィールドを作成して、それを参照すればGoogleMapsのAPIを取得せずに2地点間の距離を比較することができます。

参考サイト

WordPress REST API で、カスタム投稿タイプなどの情報を取得する | Tips Note by TAM
https://www.tam-tam.co.jp/tipsnote/cms/post9688.html

WordPressのREST APIにカスタムフィールドがないなら自分で追加すればいいじゃない | オレDEV.com
https://dev.ore-shika.com/post/wp-rest-custom-fields/

位置情報取得や2地点間距離取得についての参考サイト

位置情報 API – Web API | MDN
https://developer.mozilla.org/ja/docs/Web/API/Geolocation_API

Geolocation.getCurrentPosition() – Web API | MDN
https://developer.mozilla.org/ja/docs/Web/API/Geolocation/getCurrentPosition

地球上の2地点間の距離を取得するアルゴリズム(ヒュベニ or 球面三角法)比較【JavaScript】 | 404 motivation not found
https://tech-blog.s-yoshiki.com/entry/8/?referer=https://www.google.com/