get($url); return Json::decode($result); } //计算距离 lng经度 lat纬度 ssh 2020.5.14 public static function calculateDistance($fromLnt, $fromLat, $toLnt, $toLat) { if (getenv('YII_ENV') == 'production') { $url = "https://restapi.amap.com/v5/direction/electrobike?key=" . self::$thirdMapKey . '&origin=' . $fromLnt . ',' . $fromLat . '&destination=' . $toLnt . ',' . $toLat; } else { $url = "http://restapi.amap.com/v5/direction/electrobike?key=" . self::$thirdMapKey . '&origin=' . $fromLnt . ',' . $fromLat . '&destination=' . $toLnt . ',' . $toLat; } $curl = new curl\Curl(); $result = $curl->get($url); $result = Json::decode($result); $distance = 0; if (!empty($result['status']) && $result['status'] == 1) { $path = $result['route']['paths'] ?? []; $current = $path[0] ?? []; $distance = $current['distance'] ?? 0; } return $distance; } //计算客户到店距离,骑电动车 ssh 2026.7.30 public static function calcShopDistanceByEleBike($beginPlace, $toPlace, $report = 1) { $beginLnt = $beginPlace->long ?? ''; $beginLat = $beginPlace->lat ?? ''; $toLnt = $toPlace->long ?? ''; $toLat = $toPlace->lat ?? ''; // 校验经纬度参数格式 if (empty($beginLnt) || empty($beginLat)) { if ($report == 1) { util::fail('计算距离失败,请完善地址'); } return ['distance' => 0, 'distanceKm' => 0]; } if (!is_numeric($beginLnt) || !is_numeric($beginLat)) { if ($report == 1) { util::fail('计算距离失败,请完善地址'); } return ['distance' => 0, 'distanceKm' => 0]; } if (empty($toLnt) || empty($toLat)) { if ($report == 1) { util::fail('计算距离失败,商家地址没有完善'); } return ['distance' => 0, 'distanceKm' => 0]; } if (!is_numeric($toLnt) || !is_numeric($toLat)) { if ($report == 1) { util::fail('计算距离失败,商家地址没有完善'); } return ['distance' => 0, 'distanceKm' => 0]; } $distance = self::calculateDistance($beginLnt, $beginLat, $toLnt, $toLat); $distanceKm = bcdiv($distance, 1000, 2); return ['distance' => $distance, 'distanceKm' => $distanceKm]; } /** * 批量计算多个起点到一个终点的距离 (高德 V3 距离测量接口) * @param array $origins 起点坐标数组,例如:[['lng' => '116.481028', 'lat' => '39.989643'], ...] * @param string $toLnt 终点经度 * @param string $toLat 终点纬度 * @param int $type 路径计算的方式和方法 (0:直线距离, 1:驾车, 3:步行) * @return array */ public static function calculateBatchDistance($origins, $toLnt, $toLat, $type = 1) { if (empty($origins)) { return []; } // 将起点数组拼接成高德要求的格式: lng1,lat1|lng2,lat2 $originStrs = []; foreach ($origins as $origin) { $originStrs[] = $origin['lng'] . ',' . $origin['lat']; } $originsParam = implode('|', $originStrs); $destinationParam = $toLnt . ',' . $toLat; $domain = getenv('YII_ENV') == 'production' ? 'https://restapi.amap.com' : 'http://restapi.amap.com'; $url = $domain . "/v3/distance?key=" . self::$thirdMapKey . "&origins={$originsParam}&destination={$destinationParam}&type={$type}"; $curl = new curl\Curl(); $result = $curl->get($url); $result = \yii\helpers\Json::decode($result); $distances = []; if (!empty($result['status']) && $result['status'] == 1 && !empty($result['results'])) { foreach ($result['results'] as $index => $res) { // 返回的结果顺序与传入的 origins 顺序一致 $distances[$index] = $res['distance'] ?? 0; } } else { noticeUtil::push("高德批量测距接口返回失败:" . json_encode($result) . " url:" . $url); } return $distances; } /** * 逆地址解析(高德),根据经纬度解析省市区与地址 * @param string|float $lat 纬度 * @param string|float $long 经度 * @param string $locationType gcj02|wgs84,wgs84 时传 coordsys=gps 给高德 * @return array */ public static function resolveLocation($lat, $long, $locationType = 'gcj02') { $lat = (float)$lat; $long = (float)$long; if ($lat == 0 || $long == 0) { return []; } $domain = getenv('YII_ENV') == 'production' ? 'https://restapi.amap.com' : 'http://restapi.amap.com'; // 高德 location 参数:经度,纬度 $location = $long . ',' . $lat; $url = $domain . '/v3/geocode/regeo?location=' . urlencode($location) . '&key=' . self::$thirdMapKey . '&extensions=base'; if ($locationType === 'wgs84' || $locationType === 'gps') { $url .= '&coordsys=gps'; } $curl = new curl\Curl(); $result = $curl->get($url); $result = Json::decode($result); return self::formatAmapRegeoResult($result); } /** * 格式化高德逆地理编码结果 */ private static function formatAmapRegeoResult($result) { if (empty($result['status']) || (string)$result['status'] !== '1' || empty($result['regeocode'])) { return []; } $regeo = $result['regeocode']; $component = $regeo['addressComponent'] ?? []; $province = self::normalizeAmapRegionValue($component['province'] ?? ''); $city = self::normalizeAmapRegionValue($component['city'] ?? ''); $district = self::normalizeAmapRegionValue($component['district'] ?? ''); if (empty($city)) { $city = $province; } $showAddress = $regeo['formatted_address'] ?? ''; $township = self::normalizeAmapRegionValue($component['township'] ?? ''); $streetNumber = $component['streetNumber'] ?? []; $street = is_array($streetNumber) ? ($streetNumber['street'] ?? '') : ''; $number = is_array($streetNumber) ? ($streetNumber['number'] ?? '') : ''; $address = trim($township . $street . $number); if (empty($address)) { $address = $showAddress; if ($province && mb_strpos($address, $province) === 0) { $address = mb_substr($address, mb_strlen($province)); } if ($city && $city !== $province && mb_strpos($address, $city) === 0) { $address = mb_substr($address, mb_strlen($city)); } if ($district && mb_strpos($address, $district) === 0) { $address = mb_substr($address, mb_strlen($district)); } $address = trim($address); } if (empty($address) || empty($province) || empty($city)) { return []; } return [ 'province' => $province, 'city' => $city, 'district' => $district, 'address' => $address, 'showAddress' => $showAddress ?: ($province . $city . $district . $address), ]; } /** * 根据经纬度获取当前位置及附近 POI 列表(高德逆地理 extensions=all) */ public static function resolveNearby($lat, $long, $locationType = 'gcj02') { $lat = (float)$lat; $long = (float)$long; if ($lat == 0 || $long == 0) { return []; } $domain = getenv('YII_ENV') == 'production' ? 'https://restapi.amap.com' : 'http://restapi.amap.com'; $location = $long . ',' . $lat; $url = $domain . '/v3/geocode/regeo?location=' . urlencode($location) . '&key=' . self::$thirdMapKey . '&extensions=all&radius=1000&roadlevel=0'; if ($locationType === 'wgs84' || $locationType === 'gps') { $url .= '&coordsys=gps'; } $curl = new curl\Curl(); $result = Json::decode($curl->get($url)); if (empty($result['status']) || (string)$result['status'] !== '1' || empty($result['regeocode'])) { return []; } $regeo = $result['regeocode']; $component = $regeo['addressComponent'] ?? []; $province = self::normalizeAmapRegionValue($component['province'] ?? ''); $city = self::normalizeAmapRegionValue($component['city'] ?? ''); $district = self::normalizeAmapRegionValue($component['district'] ?? ''); if (empty($city)) { $city = $province; } $centerAddress = $regeo['formatted_address'] ?? ''; $centerName = self::normalizeAmapRegionValue($component['township'] ?? ''); if (empty($centerName)) { $centerName = '当前位置'; } $list = [[ 'name' => $centerName, 'address' => $centerAddress, 'province' => $province, 'city' => $city, 'district' => $district, 'location' => $long . ',' . $lat, 'showAddress' => $centerAddress, ]]; $pois = $regeo['pois'] ?? []; $exists = [$centerName]; foreach ($pois as $poi) { $poiName = $poi['name'] ?? ''; if ($poiName === '' || in_array($poiName, $exists, true)) { continue; } $exists[] = $poiName; $poiAddress = $poi['address'] ?? ''; if ($poiAddress === '') { $poiAddress = $centerAddress ? ($centerAddress . ' ' . $poiName) : $poiName; } $list[] = [ 'name' => $poiName, 'address' => $poiAddress, 'province' => $province, 'city' => $city, 'district' => $district, 'location' => $poi['location'] ?? ($long . ',' . $lat), 'showAddress' => $poiAddress, ]; if (count($list) >= 21) { break; } } return $list; } private static function normalizeAmapRegionValue($value) { if (is_array($value)) { return !empty($value) ? (string)$value[0] : ''; } return (string)$value; } /** * 坐标系转换:从 WGS84 转换为 GCJ-02 * @param $lng * @param $lat * @return array */ public static function wgs84ToGcj02($lng, $lat) { $lng = (float)$lng; $lat = (float)$lat; if (self::outOfChina($lng, $lat)) { return ['lng' => $lng, 'lat' => $lat]; } $a = 6378245.0; $ee = 0.00669342162296594323; $dLat = self::transformLat($lng - 105.0, $lat - 35.0); $dLng = self::transformLng($lng - 105.0, $lat - 35.0); $radLat = $lat / 180.0 * M_PI; $magic = sin($radLat); $magic = 1 - $ee * $magic * $magic; $sqrtMagic = sqrt($magic); $dLat = ($dLat * 180.0) / (($a * (1 - $ee)) / ($magic * $sqrtMagic) * M_PI); $dLng = ($dLng * 180.0) / ($a / $sqrtMagic * cos($radLat) * M_PI); return ['lng' => $lng + $dLng, 'lat' => $lat + $dLat]; } private static function transformLat($lng, $lat) { $ret = -100.0 + 2.0 * $lng + 3.0 * $lat + 0.2 * $lat * $lat + 0.1 * $lng * $lat + 0.2 * sqrt(abs($lng)); $ret += (20.0 * sin(6.0 * $lng * M_PI) + 20.0 * sin(2.0 * $lng * M_PI)) * 2.0 / 3.0; $ret += (20.0 * sin($lat * M_PI) + 40.0 * sin($lat / 3.0 * M_PI)) * 2.0 / 3.0; $ret += (160.0 * sin($lat / 12.0 * M_PI) + 320.0 * sin($lat * M_PI / 30.0)) * 2.0 / 3.0; return $ret; } private static function transformLng($lng, $lat) { $ret = 300.0 + $lng + 2.0 * $lat + 0.1 * $lng * $lng + 0.1 * $lng * $lat + 0.1 * sqrt(abs($lng)); $ret += (20.0 * sin(6.0 * $lng * M_PI) + 20.0 * sin(2.0 * $lng * M_PI)) * 2.0 / 3.0; $ret += (20.0 * sin($lng * M_PI) + 40.0 * sin($lng / 3.0 * M_PI)) * 2.0 / 3.0; $ret += (150.0 * sin($lng / 12.0 * M_PI) + 300.0 * sin($lng / 30.0 * M_PI)) * 2.0 / 3.0; return $ret; } private static function outOfChina($lng, $lat) { if ($lng < 72.004 || $lng > 137.8347) { return true; } if ($lat < 0.8293 || $lat > 55.8271) { return true; } return false; } }