GeoHash.class.php 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. <?php
  2. /**
  3. * PHP Geometry GeoHash encoder/decoder.
  4. *
  5. * @author prinsmc
  6. * @see http://en.wikipedia.org/wiki/Geohash
  7. *
  8. */
  9. class GeoHash extends GeoAdapter{
  10. private $table = "0123456789bcdefghjkmnpqrstuvwxyz";
  11. /**
  12. * Convert the geohash to a Point. The point is 2-dimensional.
  13. * @return Point the converted geohash
  14. * @param string $hash a geohash
  15. * @see GeoAdapter::read()
  16. */
  17. public function read($hash, $as_grid = FALSE) {
  18. $ll = $this->decode($hash);
  19. if (!$as_grid) {
  20. return new Point($ll['medlon'], $ll['medlat']);
  21. }
  22. else {
  23. return new Polygon(array(
  24. new LineString(array(
  25. new Point($ll['minlon'], $ll['maxlat']),
  26. new Point($ll['maxlon'], $ll['maxlat']),
  27. new Point($ll['maxlon'], $ll['minlat']),
  28. new Point($ll['minlon'], $ll['minlat']),
  29. new Point($ll['minlon'], $ll['maxlat']),
  30. ))
  31. ));
  32. }
  33. }
  34. /**
  35. * Convert the geometry to geohash.
  36. * @return string the geohash or null when the $geometry is not a Point
  37. * @param Point $geometry
  38. * @see GeoAdapter::write()
  39. */
  40. public function write(Geometry $geometry, $precision = NULL){
  41. if ($geometry->isEmpty()) return '';
  42. if($geometry->geometryType() === 'Point'){
  43. return $this->encodePoint($geometry, $precision);
  44. }
  45. else {
  46. // The geohash is the hash grid ID that fits the envelope
  47. $envelope = $geometry->envelope();
  48. $geohashes = array();
  49. $geohash = '';
  50. foreach ($envelope->getPoints() as $point) {
  51. $geohashes[] = $this->encodePoint($point, 0.0000001);
  52. }
  53. $i = 0;
  54. while ($i < strlen($geohashes[0])) {
  55. $char = $geohashes[0][$i];
  56. foreach ($geohashes as $hash) {
  57. if ($hash[$i] != $char) {
  58. return $geohash;
  59. }
  60. }
  61. $geohash .= $char;
  62. $i++;
  63. }
  64. return $geohash;
  65. }
  66. }
  67. /**
  68. * @return string geohash
  69. * @param Point $point
  70. * @author algorithm based on code by Alexander Songe <a@songe.me>
  71. * @see https://github.com/asonge/php-geohash/issues/1
  72. */
  73. private function encodePoint($point, $precision = NULL){
  74. if ($precision === NULL) {
  75. $lap = strlen($point->y())-strpos($point->y(),".");
  76. $lop = strlen($point->x())-strpos($point->x(),".");
  77. $precision = pow(10,-max($lap-1,$lop-1,0))/2;
  78. }
  79. $minlat = -90;
  80. $maxlat = 90;
  81. $minlon = -180;
  82. $maxlon = 180;
  83. $latE = 90;
  84. $lonE = 180;
  85. $i = 0;
  86. $error = 180;
  87. $hash='';
  88. while($error>=$precision) {
  89. $chr = 0;
  90. for($b=4;$b>=0;--$b) {
  91. if((1&$b) == (1&$i)) {
  92. // even char, even bit OR odd char, odd bit...a lon
  93. $next = ($minlon+$maxlon)/2;
  94. if($point->x()>$next) {
  95. $chr |= pow(2,$b);
  96. $minlon = $next;
  97. } else {
  98. $maxlon = $next;
  99. }
  100. $lonE /= 2;
  101. } else {
  102. // odd char, even bit OR even char, odd bit...a lat
  103. $next = ($minlat+$maxlat)/2;
  104. if($point->y()>$next) {
  105. $chr |= pow(2,$b);
  106. $minlat = $next;
  107. } else {
  108. $maxlat = $next;
  109. }
  110. $latE /= 2;
  111. }
  112. }
  113. $hash .= $this->table[$chr];
  114. $i++;
  115. $error = min($latE,$lonE);
  116. }
  117. return $hash;
  118. }
  119. /**
  120. * @param string $hash a geohash
  121. * @author algorithm based on code by Alexander Songe <a@songe.me>
  122. * @see https://github.com/asonge/php-geohash/issues/1
  123. */
  124. private function decode($hash){
  125. $ll = array();
  126. $minlat = -90;
  127. $maxlat = 90;
  128. $minlon = -180;
  129. $maxlon = 180;
  130. $latE = 90;
  131. $lonE = 180;
  132. for($i=0,$c=strlen($hash);$i<$c;$i++) {
  133. $v = strpos($this->table,$hash[$i]);
  134. if(1&$i) {
  135. if(16&$v)$minlat = ($minlat+$maxlat)/2; else $maxlat = ($minlat+$maxlat)/2;
  136. if(8&$v) $minlon = ($minlon+$maxlon)/2; else $maxlon = ($minlon+$maxlon)/2;
  137. if(4&$v) $minlat = ($minlat+$maxlat)/2; else $maxlat = ($minlat+$maxlat)/2;
  138. if(2&$v) $minlon = ($minlon+$maxlon)/2; else $maxlon = ($minlon+$maxlon)/2;
  139. if(1&$v) $minlat = ($minlat+$maxlat)/2; else $maxlat = ($minlat+$maxlat)/2;
  140. $latE /= 8;
  141. $lonE /= 4;
  142. } else {
  143. if(16&$v)$minlon = ($minlon+$maxlon)/2; else $maxlon = ($minlon+$maxlon)/2;
  144. if(8&$v) $minlat = ($minlat+$maxlat)/2; else $maxlat = ($minlat+$maxlat)/2;
  145. if(4&$v) $minlon = ($minlon+$maxlon)/2; else $maxlon = ($minlon+$maxlon)/2;
  146. if(2&$v) $minlat = ($minlat+$maxlat)/2; else $maxlat = ($minlat+$maxlat)/2;
  147. if(1&$v) $minlon = ($minlon+$maxlon)/2; else $maxlon = ($minlon+$maxlon)/2;
  148. $latE /= 4;
  149. $lonE /= 8;
  150. }
  151. }
  152. $ll['minlat'] = $minlat;
  153. $ll['minlon'] = $minlon;
  154. $ll['maxlat'] = $maxlat;
  155. $ll['maxlon'] = $maxlon;
  156. $ll['medlat'] = round(($minlat+$maxlat)/2, max(1, -round(log10($latE)))-1);
  157. $ll['medlon'] = round(($minlon+$maxlon)/2, max(1, -round(log10($lonE)))-1);
  158. return $ll;
  159. }
  160. }