GeohashService.php 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. <?php
  2. namespace App\Services;
  3. class GeohashService
  4. {
  5. private $coding = "0123456789bcdefghjkmnpqrstuvwxyz";
  6. private $codingMap = array();
  7. public function Geohash() {
  8. for($i = 0; $i < 32; $i++) {
  9. $this->codingMap[substr($this->coding, $i, 1)] = str_pad(decbin($i), 5, "0", STR_PAD_LEFT);
  10. }
  11. }
  12. public function decode($hash) {
  13. $binary = "";
  14. $hl = strlen($hash);
  15. for($i = 0; $i < $hl; $i++) {
  16. $binary .= $this->codingMap[substr($hash, $i, 1)];
  17. }
  18. $bl = strlen($binary);
  19. $blat = "";
  20. $blong = "";
  21. for ($i = 0; $i < $bl; $i++) {
  22. if ($i%2) {
  23. $blat = $blat.substr($binary, $i, 1);
  24. } else {
  25. $blong = $blong.substr($binary, $i, 1);
  26. }
  27. }
  28. $lat = $this->binDecode($blat, -90, 90);
  29. $long = $this->binDecode($blong, -180, 180);
  30. $latErr = $this->calcError(strlen($blat), -90, 90);
  31. $longErr = $this->calcError(strlen($blong), -180, 180);
  32. $latPlaces = max(1, -round(log10($latErr))) - 1;
  33. $longPlaces = max(1, -round(log10($longErr))) - 1;
  34. $lat = round($lat, $latPlaces);
  35. $long = round($long, $longPlaces);
  36. return array($lat,$long);
  37. }
  38. public function encode($lat,$long) {
  39. $plat = $this->precision($lat);
  40. $latbits = 1;
  41. $err = 45;
  42. while($err > $plat) {
  43. $latbits++;
  44. $err/= 2;
  45. }
  46. $plong = $this->precision($long);
  47. $longbits = 1;
  48. $err = 90;
  49. while($err > $plong) {
  50. $longbits++;
  51. $err /= 2;
  52. }
  53. $bits = max($latbits,$longbits);
  54. $longbits = $bits;
  55. $latbits = $bits;
  56. $addlong = 1;
  57. while (($longbits+$latbits) % 5 != 0) {
  58. $longbits += $addlong;
  59. $latbits += !$addlong;
  60. $addlong = !$addlong;
  61. }
  62. $blat = $this->binEncode($lat, -90, 90, $latbits);
  63. $blong = $this->binEncode($long, -180, 180, $longbits);
  64. $binary = "";
  65. $uselong = 1;
  66. while (strlen($blat)+strlen($blong)) {
  67. if ($uselong) {
  68. $binary = $binary.substr($blong, 0, 1);
  69. $blong = substr($blong, 1);
  70. } else {
  71. $binary = $binary.substr($blat, 0, 1);
  72. $blat = substr($blat, 1);
  73. }
  74. $uselong = !$uselong;
  75. }
  76. $hash = "";
  77. for ($i = 0; $i < strlen($binary); $i += 5) {
  78. $n = bindec(substr($binary, $i, 5));
  79. $hash = $hash . $this->coding[$n];
  80. }
  81. return $hash;
  82. }
  83. private function calcError($bits, $min, $max) {
  84. $err = ($max - $min) / 2;
  85. while ($bits--) {
  86. $err /= 2;
  87. }
  88. return $err;
  89. }
  90. private function precision($number) {
  91. $precision = 0;
  92. $pt = strpos($number,'.');
  93. if ($pt!== false) {
  94. $precision = -(strlen($number) - $pt - 1);
  95. }
  96. return pow(10, $precision) / 2;
  97. }
  98. private function binEncode($number, $min, $max, $bitcount) {
  99. if ($bitcount == 0) {
  100. return "";
  101. }
  102. $mid = ($min + $max) / 2;
  103. if ($number > $mid) {
  104. return "1" . $this->binEncode($number, $mid, $max, $bitcount - 1);
  105. } else {
  106. return "0" . $this->binEncode($number, $min, $mid, $bitcount - 1);
  107. }
  108. }
  109. private function binDecode($binary, $min, $max) {
  110. $mid = ($min + $max) / 2;
  111. if (strlen($binary) == 0) {
  112. return $mid;
  113. }
  114. $bit = substr($binary, 0, 1);
  115. $binary = substr($binary, 1);
  116. if ($bit == 1) {
  117. return $this->binDecode($binary, $mid, $max);
  118. } else {
  119. return $this->binDecode($binary, $min, $mid);
  120. }
  121. }
  122. }