[TOC] #### 1. 前言 --- 想要測(cè)試本文提供的幾個(gè)功能函數(shù),可以使用下面這個(gè)數(shù)據(jù)表結(jié)構(gòu)及其數(shù)據(jù) ```sql CREATE TABLE `user` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '用戶id', `name` varchar(60) DEFAULT NULL COMMENT '昵稱', `longitude` varchar(64) DEFAULT NULL COMMENT '經(jīng)度', `latitude` varchar(64) DEFAULT NULL COMMENT '緯度', `remark` varchar(50) DEFAULT NULL COMMENT '備注', `distance` varchar(20) DEFAULT NULL COMMENT '距離', PRIMARY KEY (`id`) ) ENGINE=InnoDB COMMENT='用戶表'; INSERT INTO `user` (`name`, `longitude`, `latitude`, `remark`, `distance`) VALUES ('中海九號(hào)公館', '113.899529', '22.60063', '深圳市寶安區(qū)中海九號(hào)公館', '3.66km'); INSERT INTO `user` (`name`, `longitude`, `latitude`, `remark`, `distance`) VALUES ('平巒山公園', '113.876462', '22.608322', '深圳市寶安區(qū)平巒山公園', '2.88km'); INSERT INTO `user` (`name`, `longitude`, `latitude`, `remark`, `distance`) VALUES ('鐵仔山公園', '113.86359', '22.592355', '深圳市寶安區(qū)鐵仔山公園', '1.16km'); INSERT INTO `user` (`name`, `longitude`, `latitude`, `remark`, `distance`) VALUES ('寶安公園', '113.902671', '22.58621', '深圳市寶安區(qū)寶安公園', '3.45km'); ``` 本文內(nèi)容測(cè)試各個(gè)功能函數(shù)時(shí),使用的當(dāng)前位置坐標(biāo)均為: ```php // 深圳市寶安區(qū)西鄉(xiāng)街道九方廣場(chǎng) $longitude = '113.869205';//經(jīng)度 $latitude = '22.583286';//緯度 ``` #### 2. 計(jì)算經(jīng)緯度坐標(biāo)間的距離 --- `計(jì)算經(jīng)緯度坐標(biāo)間的距離` 功能函數(shù) (前四個(gè)參數(shù)為兩組經(jīng)緯度坐標(biāo)) ```php /** * 計(jì)算經(jīng)緯度坐標(biāo)間的距離 * @param $lng1 經(jīng)度 * @param $lat1 緯度 * @param $lng2 經(jīng)度 * @param $lat2 緯度 * @param $lang 語(yǔ)言 */ function get_distance($lng1, $lat1, $lng2, $lat2, $lang = 'en') { // 地球的近似半徑(單位:米) $earthRadius = 6367000; // 將這些度數(shù)轉(zhuǎn)換為弧度以使用公式 $lat1 = ($lat1 * pi()) / 180; $lng1 = ($lng1 * pi()) / 180; $lat2 = ($lat2 * pi()) / 180; $lng2 = ($lng2 * pi()) / 180; // 使用 Haversine 公示計(jì)算距離 // http://en.wikipedia.org/wiki/Haversine_formula $calcLongitude = $lng2 - $lng1; $calcLatitude = $lat2 - $lat1; $stepOne = pow(sin($calcLatitude / 2), 2) + cos($lat1) * cos($lat2) * pow(sin($calcLongitude / 2), 2); $stepTwo = 2 * asin(min(1, sqrt($stepOne))); // 兩個(gè)經(jīng)緯度坐標(biāo)的距離(單位: 米) $calculatedDistance = round($earthRadius * $stepTwo); // 距離單位 $language = [ 'en' => ['m' => 'm', 'km' => 'km'], 'cn' => ['m' => '米', 'km' => '公里'], ]; if (!isset($language[$lang])) throw new \Exception('不支持的語(yǔ)言:' . $lang); foreach ($language[$lang] as $key => $value) $$key = $value; // 兩個(gè)坐標(biāo)間的距離,單位:米 $distance = round($calculatedDistance); // 距離單位轉(zhuǎn)換:超出 1000m 時(shí)單位轉(zhuǎn)為km if ($distance < 1000) { $distance .= $m; } else { $distance = floatval(number_format($distance / 1000, 2)) . $km; } return $distance; // 返回單位轉(zhuǎn)換后的距離 } ``` **使用示例:** 我在 `九方廣場(chǎng)`,手機(jī)上的高德地圖導(dǎo)航至 `中海九號(hào)公館` 顯示的距離為 `3.6公里`,計(jì)算結(jié)果還是很準(zhǔn)確的 ```php // 深圳市寶安區(qū)西鄉(xiāng)街道九方廣場(chǎng): 113.869205, 22.583286 // 深圳市寶安區(qū)西鄉(xiāng)街道中海九號(hào)公館: 113.899529, 22.60063 $distance = get_distance(113.869205, 22.583286, 113.899529, 22.60063); echo $distance; //3.66km ``` #### 3. 根據(jù)經(jīng)緯度坐標(biāo)距離排序 --- 項(xiàng)目中經(jīng)常有距離顯示數(shù)據(jù)的場(chǎng)景,根據(jù)距離排序,越近越靠前顯示;比如: 店鋪地址、房源信息等。代碼示例: ```php // 當(dāng)前坐標(biāo) $longitude = '113.869205'; $latitude = '22.583286'; // 數(shù)據(jù)庫(kù)中經(jīng)緯度字段分別為:longitude、latitude $field = '*,( 2 * 6378.137 * ASIN( SQRT( POW( SIN( PI() * (' . $longitude . ' - longitude) / 360 ), 2 ) + COS(PI() * ' . $latitude . ' / 180) * COS(latitude * PI() / 180) * POW( SIN( PI() * (' . $latitude . ' - latitude) / 360 ), 2 ) ) ) ) AS juli'; // 根據(jù)距離升序查詢(越近越靠前) $order = 'juli asc,id desc'; // 查詢數(shù)據(jù) Db::name('user')->field($field)->order($order)->select(); ``` #### 4. 經(jīng)緯度范圍查詢 --- `經(jīng)緯度范圍計(jì)算` 功能函數(shù) ```php /** * 經(jīng)緯度范圍計(jì)算 * @param $longitude 經(jīng)度 * @param $latitude 緯度 * @param $radius 半徑(米) * @return array */ function get_around($longitude, $latitude, $radius) { $PI = 3.14159265; $degree = (24901 * 1609) / 360.0; $dpmLat = 1 / $degree; $radiusLat = $dpmLat * $radius; $minLat = $latitude - $radiusLat; $maxLat = $latitude + $radiusLat; $mpdLng = $degree * cos($latitude * ($PI / 180)); $dpmLng = 1 / $mpdLng; $radiusLng = $dpmLng * $radius; $minLng = $longitude - $radiusLng; $maxLng = $longitude + $radiusLng; return compact('minLat', 'maxLat', 'minLng', 'maxLng'); } ``` **使用示例** 查詢 3 公里內(nèi)的數(shù)據(jù)。首先,根據(jù)當(dāng)前位置獲取 3 公里內(nèi)的經(jīng)緯度范圍,然后帶上查詢條件查詢數(shù)據(jù)庫(kù)即可 ```php $longitude = 113.869205; //經(jīng)度 $latitude = 22.583286; //緯度 $radius = 3000; //單位:米 // 經(jīng)緯度范圍 $around = get_around($longitude, $latitude, $radius); // 構(gòu)造查詢條件 // 數(shù)據(jù)庫(kù)經(jīng)緯度字段分別為:longitude,latitude $where = [ ['longitude', '>=', $around['minLng']], ['longitude', '<=', $around['maxLng']], ['latitude', '>=', $around['minLat']], ['latitude', '<=', $around['maxLat']], ]; // 按照經(jīng)緯度范圍查詢數(shù)據(jù) // 建議使用 where 的閉包查詢(TP6.0) // 因?yàn)殚]包可以生成以下SQL,標(biāo)明這幾個(gè)查詢條件是一個(gè)整體,便于后期維護(hù) // SQL語(yǔ)句示例: SELECT * FROM `user` WHERE ( 經(jīng)緯度查詢條件 ) and 其他條件 $data = Db::name('user') ->where(function ($query) use ($where) { $query->where($where); }) ->select(); ```