Advanced Custom Fields 便利です。
Google Mapフィールドにすれば、投稿時も記事でもさくっとGoogle Mapが使えるのはいいんですが、値をシリアライズしてカスタムフィールドに格納してしまうので、meta_queryで使いづらい・・・ ぞと。
目次
緯度と経度単体のカスタムフィールド化で解決
記事保存時のアクション wp_insert_post をフックして、緯度、経度単体のカスタムフィールドとして値を別途保存すればいい感じになります。
これで get_field() や、 get_post_meta() で latと lngが取り出せるし、meta_query指定で抽出も簡単です。
1 2 3 4 5 6 7 8 9 10 |
// 記事投稿時に緯度経度を別途保存 function update_acf_googlemap_latlng($id) { // ACFのGoogleMapフィールド "acf-googlemap" があったら、lat lng を別途作成する if ($location = get_field('acf-googlemap', $id)) { update_post_meta($id, 'lat', $location['lat']) ; update_post_meta($id, 'lng', $location['lng']) ; } } // アクションをフック add_action('wp_insert_post', 'update_acf_googlemap_latlng'); |
緯度経度は準備できた! 実際に使いたいのはたぶん、こんなケース?
緯度経度をピンポイントでズバリ! の検索をすることはほとんど無くて、
実際に検索を利用したいケースを想定するならば、
お店紹介のブログやらで「このお店も近くにあります!」的な関連表示をしたい!
とか、そんなところでしょうか。
MySQL的に記述の2点間のなんとなくな距離は
SQRT(POWER(ABS(lat1 – lat2), 2) + POWER(ABS(lng1 – lng2), 2))
だと思うんですが、これをORDER BY指定に・・・ と思っても
WP_Queryにこれ、さくっと渡せないんですよねー。
渡せないですよね? ね?
渡せるのであれば、おまるくんの無駄骨を笑いつつ、そっとページを閉じるのを推奨です・・・。
やっぱり渡せないのであれば、この後の記事を参考にするといいぜ!!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
// この記事の関連記事を表示(距離的な意味で) global $post; $lat = get_post_meta($post->ID, 'lat', true) * 1.0; $lng = get_post_meta($post->ID, 'lng', true) * 1.0; // 経度緯度ともに+-1度以内の範囲指定をして抽出される場所を限定 $range = 1.0; $args=array( 'post__not_in' => array($post->ID), // この記事を除く 'showposts'=> 5, // 近い順に5件を取得 'meta_query' => array( 'lng' => array( 'key' => 'lng', 'value' => array($lng - $range, $lng + $range), 'type' => 'DECIMAL', 'compare' => 'BETWEEN' ), 'lat' => array( 'key' => 'lat', 'value' => array($lat - $range, $lat + $range), 'type' => 'DECIMAL', 'compare' => 'BETWEEN' ) ) ,'orderby' => 'ここに距離順に出来るORDER BY がかけたらいいのに!', ,'order' => 'ASC' ); $my_query = new WP_Query($args); if($my_query->have_posts()) { // 近くにこのお店もあります! の記事見出しを表示 } |
まずはDECIMALの扱いを補正
まずORDER BY で計算して重たくなるの嫌なので、あらかじめ対象件数少なくしよう。
と思って、まずは緯度経度をBETWEEENであらかじめ絞り込み!
が、うまくいかないという悲しい事態になります。
どうやら、カスタムフィールドのDECIMALは、クエリ生成時にCAST(value AS DECIMAL)にキャストされているらしく、小数点以下が丸め込まれてしまうんですね。
get_meta_sql でWHERE句を小数点以下6桁までよろしくお願いします!
なフィルターフックで対応。
同様の事が ORDER BY句でも起きるので、こちらも posts_orderby で対応しておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// order by時のDECIMAL CAST精度を補正 function fix_order_by_decimal_precision($orderby) { return str_replace('DECIMAL', 'DECIMAL(10,6)', $orderby); } // フィルターをフック add_filter('posts_orderby', 'fix_order_by_decimal_precision'); // get_meta_sql時のDECIMAL CAST精度を補正 function fix_get_meta_sql_decimal_precision($array) { $array['where'] = str_replace('DECIMAL', 'DECIMAL(10,6)', $array['where']); return $array; } // フィルターをフック add_filter('get_meta_sql', 'fix_get_meta_sql_decimal_precision'); |
ぼくのかんがえたWP_Queryクラス
ORDER BY指定にさくっとちょっと長ったらしい感じの指定ができないのは、
$orderbyの中身をurldecode(explode(‘ ‘, $orderby))して、最後にjoin(‘,’, $orderby) しているせい。
そのおかげで+やらスペースやらが思った通りに渡せていないのです。
WP_Queryクラスを継承して、小さな親切余計なお世話ばりの変換を無くす派生クラスを作ってみます。
余計なお世話が起きている本丸はparse_orderbyメソッド。
便宜上スペースを無くしてもいいんですが、urlencodeの利用を生かし、
スペースが+変換されてしまうのを防ぐために事前にタブに変換してしまうスタティックメソッド encodeも追加しました。
利用の際は、このメソッドで変換をかけながら ORDER BYを指定します。
WP_Query本来の ORDER BY指定については、{}でくくることで対応します。
orderby => ‘ID‘ やら orderby=> ‘rand’ はこのクラスでは
orderby => ‘{ID}’ やら orderby => ‘{rand}’ と表現する感じですね。
{}外はそのままの文字がORDER BY 句の表現になるよ なクラスです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
// order by 句をカスタマイズするよクラス class WP_Query_UseCustomOrderBy extends WP_Query { /* orderby 指定をWP_Queryの内部変換に負けない子にする orderby => 'hogehoge' と書く部分を orderby => WP_Query_UseCustomOrderBy::encode('hogehoge') として利用 */ public static function encode($orderby) { // 既存の \tの存在は未配慮 return urlencode(str_replace(' ', "\t", $orderby)); } /* カスタムOrderBy適用 parse_orderby {} でくくった部分は 従来のparse_orderbyで評価する それ以外はそのままの文字を引き渡し */ protected function parse_orderby($orderby) { $parse_orderby = function($o) { $o = $o[0]; $o = preg_replace('/^\{/', '', $o); $o = preg_replace('/\}$/', '', $o); $o = parent::parse_orderby($o); return $o; }; $orderby = preg_replace_callback('/\{.*?\}/', $parse_orderby, $orderby); $orderby = str_replace("\t", ' ', $orderby); return $orderby; } |
あの場所との距離は判る、君のとの距離は判らずじまい
早速できたクラスを使って、ORDER BYに距離順を設定してみます。
ORDER取るだけなので SQRTはいらないかな?
これで距離的な抽出はばっちり!
君との距離はまだ、わからないけれど・・・。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
// この記事の関連記事を表示(距離的な意味で) global $post; $lat = get_post_meta($post->ID, 'lat', true) * 1.0; $lng = get_post_meta($post->ID, 'lng', true) * 1.0; // 経度緯度ともに+-1度以内の範囲指定をして抽出される場所を限定 $range = 1.0; $args=array( 'post__not_in' => array($post->ID), // この記事を除く 'showposts'=> 5, // 近い順に5件を取得 'meta_query' => array( 'lng' => array( 'key' => 'lng', 'value' => array($lng - $range, $lng + $range), 'type' => 'DECIMAL', 'compare' => 'BETWEEN' ), 'lat' => array( 'key' => 'lat', 'value' => array($lat - $range, $lat + $range), 'type' => 'DECIMAL', 'compare' => 'BETWEEN' ) ) ,'orderby' => WP_Query_UseCustomOrderBy::encode('(POWER(ABS({lat} - ' . $lat . '), 2) + POWER(ABS({lng} - ' . $lng .'), 2))'), ,'order' => 'ASC' ); $my_query = new WP_Query_UseCustomOrderBy($args); if($my_query->have_posts()) { // 近くにこのお店もあります! の記事見出しを表示 } |