GeoCoordinate.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. <?php
  2. /**
  3. * 地图坐标转换工具
  4. *
  5. * WGS-84:是国际标准,GPS坐标(Google Earth使用、或者GPS模块)
  6. * GCJ-02:中国坐标偏移标准,Google Map、高德、腾讯使用
  7. * BD-09:百度坐标偏移标准,Baidu Map使用
  8. * Web Mercator 网络墨卡托投影坐标,Web Mercator是一个投影坐标系统,其基准面是 WGS 1984 ;
  9. *
  10. * 更多坐标信息请查看相关官方文档
  11. * 腾讯坐标 https://lbs.qq.com/webservice_v1/guide-convert.html
  12. * 百度坐标 http://lbsyun.baidu.com/index.php?title=webapi/guide/changeposition
  13. * 百度坐标拾取反查 http://api.map.baidu.com/lbsapi/getpoint/index.html
  14. * 高德坐标拾取反查 https://lbs.amap.com/console/show/picker
  15. * GPS坐标反查【需要翻墙】 http://geohash.org/
  16. * @Author: yangmengnan
  17. * @Date: 2019-03-07 14:36:30
  18. * @Last Modified 2019-03-07
  19. */
  20. class Tools_GeoCoordinate
  21. {
  22. private $PI = 3.14159265358979324;
  23. private $x_pi = 0;
  24. public function __construct()
  25. {
  26. $this->x_pi = 3.14159265358979324 * 3000.0 / 180.0;
  27. }
  28. /**
  29. * GPS坐标转国测局火星坐标
  30. * WGS-84 to GCJ-02
  31. * @param [type] $gps_lat [纬度]
  32. * @param [type] $gps_lng [经度]
  33. * @return [type] [description]
  34. */
  35. public function gpsToGcj02($gps_lat, $gps_lng) {
  36. //if ($this->outOfChina($gps_lat, $gps_lng))
  37. if (!$this->isInChina($gps_lat, $gps_lng)) {
  38. return array('lat' => $gps_lat, 'lng' => $gps_lng);
  39. }
  40. $d = $this->delta($gps_lat, $gps_lng);
  41. return array('lat' => $gps_lat + $d['lat'],'lng' => $gps_lng + $d['lng']);
  42. }
  43. /**
  44. * 国测局火星坐标转GPS坐标
  45. * GCJ-02 to WGS-84
  46. * @param [type] $gcj_lat [description]
  47. * @param [type] $gcj_lng [description]
  48. * @return [type] [description]
  49. */
  50. public function gcj02ToGps($gcj_lat, $gcj_lng) {
  51. //if ($this->outOfChina($gcj_lat, $gcj_lng))
  52. if (!$this->isInChina($gcj_lat, $gcj_lng)) {
  53. return array('lat' => $gcj_lat, 'lng' => $gcj_lng);
  54. }
  55. $d = $this->delta($gcj_lat, $gcj_lng);
  56. return array('lat' => $gcj_lat - $d['lat'], 'lng' => $gcj_lng - $d['lng']);
  57. }
  58. /**
  59. * 国测局坐标转GPS坐标 精确算法
  60. * GCJ-02 to WGS-84 exactly
  61. * @param [type] $gcj_lat [description]
  62. * @param [type] $gcj_lng [description]
  63. * @return [type] [description]
  64. */
  65. public function gcj02ToGpsExactly($gcj_lat, $gcj_lng) {
  66. $initDelta = 0.01;
  67. $threshold = 0.000000001;
  68. $dLat = $initDelta; $dLng = $initDelta;
  69. $mLat = $gcj_lat - $dLat; $mLon = $gcj_lng - $dLng;
  70. $pLat = $gcj_lat + $dLat; $pLon = $gcj_lng + $dLng;
  71. $gps_lat = 0; $gps_lng = 0; $i = 0;
  72. while (true) {
  73. $gps_lat = ($mLat + $pLat) / 2;
  74. $gps_lng = ($mLon + $pLon) / 2;
  75. $tmp = $this->gpsToGcj02($gps_lat, $gps_lng);
  76. $dLat = $tmp['lat'] - $gcj_lat;
  77. $dLng = $tmp['lng'] - $gcj_lng;
  78. if ((abs($dLat) < $threshold) && (abs($dLng) < $threshold)) {
  79. break;
  80. }
  81. if ($dLat > 0) {
  82. $pLat = $gps_lat;
  83. } else {
  84. $mLat = $gps_lat;
  85. }
  86. if ($dLng > 0) {
  87. $pLon = $gps_lng;
  88. } else {
  89. $mLon = $gps_lng;
  90. }
  91. $i++;
  92. if ($i > 10000) {
  93. break;
  94. }
  95. }
  96. //console.log(i);
  97. return array('lat' => $gps_lat, 'lng'=> $gps_lng);
  98. }
  99. /**
  100. * 国测局坐标转百度坐标
  101. * GCJ-02 to BD-09
  102. * 中国正常坐标GCJ-02(火星,高德) 坐标转换成 BD-09(百度) 坐标
  103. * 腾讯地图用的也是GCJ02坐标
  104. * @param [double] $gcj_lat [纬度]
  105. * @param [double] $gcj_lng [经度]
  106. * @return [type] [description]
  107. */
  108. public function gcj02ToBd09($gcj_lat, $gcj_lng) {
  109. $x = $gcj_lng; $y = $gcj_lat;
  110. $z = sqrt($x * $x + $y * $y) + 0.00002 * sin($y * $this->x_pi);
  111. $theta = atan2($y, $x) + 0.000003 * cos($x * $this->x_pi);
  112. $bd_lng = $z * cos($theta) + 0.0065;
  113. $bd_lat = $z * sin($theta) + 0.006;
  114. return array('lat' => $bd_lat,'lng' => $bd_lng);
  115. }
  116. /**
  117. * 百度坐标转国测局坐标
  118. * BD-09 to GCJ-02
  119. * BD-09(百度) 坐标转换成 GCJ-02(火星,高德) 坐标
  120. * @param [type] $bd_lat [百度纬度]
  121. * @param [type] $bd_lng [百度经度]
  122. * @return [type] [description]
  123. */
  124. public function bd09ToGcj02($bd_lat, $bd_lng)
  125. {
  126. $x = $bd_lng - 0.0065; $y = $bd_lat - 0.006;
  127. $z = sqrt($x * $x + $y * $y) - 0.00002 * sin($y * $this->x_pi);
  128. $theta = atan2($y, $x) - 0.000003 * cos($x * $this->x_pi);
  129. $gcj_lng = $z * cos($theta);
  130. $gcj_lat = $z * sin($theta);
  131. return array('lat' => $gcj_lat, 'lng' => $gcj_lng);
  132. }
  133. /**
  134. * 百度bd-09坐标转为 gps wgs-84坐标
  135. *
  136. * 使用工具 \tekintian\geo_utils\GeoCoordinate
  137. * @param [type] $bd_lat [百度纬度]
  138. * @param [type] $bd_lng [百度经度]
  139. * @return [array] [GPS经纬度坐标数组]
  140. */
  141. public function bd09ToGps($bd_lat,$bd_lng)
  142. {
  143. $_gcj=$this->bd09ToGcj02($bd_lat,$bd_lng);
  144. $gps=$this->gcj02ToGpsExactly($_gcj['lat'],$_gcj['lng']);
  145. return $gps;
  146. }
  147. /**
  148. * gps wgs-84坐标转为 百度bd-09坐标
  149. *
  150. * 使用工具 \tekintian\geo_utils\GeoCoordinate
  151. * @param [type] $bd_lat [gps纬度]
  152. * @param [type] $bd_lng [gps经度]
  153. * @return [array] [bd-09经纬度坐标数组]
  154. */
  155. public function gpsToBd09($gps_lat, $gps_lng)
  156. {
  157. $_gcj=$this->gpsToGcj02($gps_lat, $gps_lng);
  158. $gps=$this->gcj02ToBd09($_gcj['lat'],$_gcj['lng']);
  159. return $gps;
  160. }
  161. /**
  162. * WGS-84 to Web mercator
  163. * $mercator_lat -> y $mercator_lng -> x
  164. * @param [type] $gps_lat [description]
  165. * @param [type] $gps_lng [description]
  166. * @return [type] [description]
  167. */
  168. public function gpsToMercator($gps_lat, $gps_lng)
  169. {
  170. $x = $gps_lng * 20037508.34 / 180.;
  171. $y = log(tan((90. + $gps_lat) * $this->PI / 360.)) / ($this->PI / 180.);
  172. $y = $y * 20037508.34 / 180.;
  173. return array('lat' => $y, 'lng' => $x);
  174. }
  175. /**
  176. *
  177. * Web mercator to WGS-84
  178. * $mercator_lat -> y $mercator_lng -> x
  179. * @param [type] $mercator_lat [description]
  180. * @param [type] $mercator_lng [description]
  181. * @return [type] [description]
  182. */
  183. public function mercatorToGps($mercator_lat, $mercator_lng)
  184. {
  185. $x = $mercator_lng / 20037508.34 * 180.;
  186. $y = $mercator_lat / 20037508.34 * 180.;
  187. $y = 180 / $this->PI * (2 * atan(exp($y * $this->PI / 180.)) - $this->PI / 2);
  188. return array('lat' => $y, 'lng' => $x);
  189. }
  190. /**
  191. * two point's distance
  192. * @param [type] $lat1 [纬度]
  193. * @param [type] $lng1 [经度]
  194. * @param [type] $lat2 [description]
  195. * @param [type] $lng2 [description]
  196. * @return [type] [description]
  197. */
  198. public function distance($lat1, $lng1, $lat2, $lng2)
  199. {
  200. $earthR = 6371000.;
  201. $x = cos($lat1 * $this->PI / 180.) * cos($lat2 * $this->PI / 180.) * cos(($lng1 - $lng2) * $this->PI / 180);
  202. $y = sin($lat1 * $this->PI / 180.) * sin($lat2 * $this->PI / 180.);
  203. $s = $x + $y;
  204. if ($s > 1) {
  205. $s = 1;
  206. }
  207. if ($s < -1) {
  208. $s = -1;
  209. }
  210. $alpha = acos($s);
  211. $distance = $alpha * $earthR;
  212. return $distance;
  213. }
  214. /**
  215. * [delta description]
  216. * @param [type] $lat [description]
  217. * @param [type] $lng [description]
  218. * @return [type] [description]
  219. */
  220. private function delta($lat, $lng)
  221. {
  222. $a = 6378245.0;// a: 卫星椭球坐标投影到平面地图坐标系的投影因子。
  223. $ee = 0.00669342162296594323;// ee: 椭球的偏心率。
  224. $dLat = $this->transformLat($lng - 105.0, $lat - 35.0);
  225. $dLng = $this->transformLng($lng - 105.0, $lat - 35.0);
  226. $radLat = $lat / 180.0 * $this->PI;
  227. $magic = sin($radLat);
  228. $magic = 1 - $ee * $magic * $magic;
  229. $sqrtMagic = sqrt($magic);
  230. $dLat = ($dLat * 180.0) / (($a * (1 - $ee)) / ($magic * $sqrtMagic) * $this->PI);
  231. $dLng = ($dLng * 180.0) / ($a / $sqrtMagic * cos($radLat) * $this->PI);
  232. return array('lat' => $dLat, 'lng' => $dLng);
  233. }
  234. /**
  235. * [rectangle description]
  236. * @param [type] $lng1 [description]
  237. * @param [type] $lat1 [description]
  238. * @param [type] $lng2 [description]
  239. * @param [type] $lat2 [description]
  240. * @return [type] [description]
  241. */
  242. private function rectangle($lng1, $lat1, $lng2, $lat2) {
  243. return array(
  244. 'west' => min($lng1, $lng2),
  245. 'north' => max($lat1, $lat2),
  246. 'east' => max($lng1, $lng2),
  247. 'south' => min($lat1, $lat2),
  248. );
  249. }
  250. /**
  251. * [isInRect description]
  252. * @param [type] $rect [description]
  253. * @param [type] $lng [description]
  254. * @param [type] $lat [description]
  255. * @return boolean [description]
  256. */
  257. private function isInRect($rect, $lng, $lat) {
  258. return $rect['west'] <= $lng && $rect['east'] >= $lng && $rect['north'] >= $lat && $rect['south'] <= $lat;
  259. }
  260. /**
  261. * [isInChina description]
  262. * @param [type] $lat [description]
  263. * @param [type] $lng [description]
  264. * @return boolean [description]
  265. */
  266. private function isInChina($lat, $lng) {
  267. //China region - raw data
  268. //http://www.cnblogs.com/Aimeast/archive/2012/08/09/2629614.html
  269. $region = array(
  270. $this->rectangle(79.446200, 49.220400, 96.330000,42.889900),
  271. $this->rectangle(109.687200, 54.141500, 135.000200, 39.374200),
  272. $this->rectangle(73.124600, 42.889900, 124.143255, 29.529700),
  273. $this->rectangle(82.968400, 29.529700, 97.035200, 26.718600),
  274. $this->rectangle(97.025300, 29.529700, 124.367395, 20.414096),
  275. $this->rectangle(107.975793, 20.414096, 111.744104, 17.871542),
  276. );
  277. //China excluded region - raw data
  278. $exclude = array(
  279. $this->rectangle(119.921265, 25.398623, 122.497559, 21.785006),
  280. $this->rectangle(101.865200, 22.284000, 106.665000, 20.098800),
  281. $this->rectangle(106.452500, 21.542200, 108.051000, 20.487800),
  282. $this->rectangle(109.032300, 55.817500, 119.127000, 50.325700),
  283. $this->rectangle(127.456800, 55.817500, 137.022700, 49.557400),
  284. $this->rectangle(131.266200, 44.892200, 137.022700, 42.569200),
  285. );
  286. for ($i = 0; $i < count($region); $i++) {
  287. if ($this->isInRect($region[$i], $lng, $lat))
  288. {
  289. for ($j = 0; $j<count($exclude); $j++) {
  290. if ($this->isInRect($exclude[$j], $lng, $lat))
  291. {
  292. return false;
  293. }
  294. }
  295. return true;
  296. }
  297. }
  298. return false;
  299. }
  300. /**
  301. * [outOfChina description]
  302. * @param [type] $lat [description]
  303. * @param [type] $lng [description]
  304. * @return [type] [description]
  305. */
  306. private function outOfChina($lat, $lng)
  307. {
  308. if ($lng < 72.004 || $lng > 137.8347)
  309. {
  310. return true;
  311. }
  312. if ($lat < 0.8293 || $lat > 55.8271)
  313. {
  314. return true;
  315. }
  316. return false;
  317. }
  318. /**
  319. * [transformLat description]
  320. * @param [type] $x [description]
  321. * @param [type] $y [description]
  322. * @return [type] [description]
  323. */
  324. private function transformLat($x, $y) {
  325. $ret = -100.0 + 2.0 * $x + 3.0 * $y + 0.2 * $y * $y + 0.1 * $x * $y + 0.2 * sqrt(abs($x));
  326. $ret += (20.0 * sin(6.0 * $x * $this->PI) + 20.0 * sin(2.0 * $x * $this->PI)) * 2.0 / 3.0;
  327. $ret += (20.0 * sin($y * $this->PI) + 40.0 * sin($y / 3.0 * $this->PI)) * 2.0 / 3.0;
  328. $ret += (160.0 * sin($y / 12.0 * $this->PI) + 320 * sin($y * $this->PI / 30.0)) * 2.0 / 3.0;
  329. return $ret;
  330. }
  331. /**
  332. * [transformLng description]
  333. * @param [type] $x [description]
  334. * @param [type] $y [description]
  335. * @return [type] [description]
  336. */
  337. private function transformLng($x, $y) {
  338. $ret = 300.0 + $x + 2.0 * $y + 0.1 * $x * $x + 0.1 * $x * $y + 0.1 * sqrt(abs($x));
  339. $ret += (20.0 * sin(6.0 * $x * $this->PI) + 20.0 * sin(2.0 * $x * $this->PI)) * 2.0 / 3.0;
  340. $ret += (20.0 * sin($x * $this->PI) + 40.0 * sin($x / 3.0 * $this->PI)) * 2.0 / 3.0;
  341. $ret += (150.0 * sin($x / 12.0 * $this->PI) + 300.0 * sin($x / 30.0 * $this->PI)) * 2.0 / 3.0;
  342. return $ret;
  343. }
  344. }