geohash.php
Go to the documentation of this file.
00001 <?php 00029 class Geohash 00030 { 00031 private $coding="0123456789bcdefghjkmnpqrstuvwxyz"; 00032 private $codingMap=array(); 00033 00034 public function Geohash() 00035 { 00036 //build map from encoding char to 0 padded bitfield 00037 for($i=0; $i<32; $i++) 00038 { 00039 $this->codingMap[substr($this->coding,$i,1)]=str_pad(decbin($i), 5, "0", STR_PAD_LEFT); 00040 } 00041 00042 } 00043 00047 public function decode($hash) 00048 { 00049 //decode hash into binary string 00050 $binary=""; 00051 $hl=strlen($hash); 00052 for($i=0; $i<$hl; $i++) 00053 { 00054 $binary.=$this->codingMap[substr($hash,$i,1)]; 00055 } 00056 00057 //split the binary into lat and log binary strings 00058 $bl=strlen($binary); 00059 $blat=""; 00060 $blong=""; 00061 for ($i=0; $i<$bl; $i++) 00062 { 00063 if ($i%2) 00064 $blat=$blat.substr($binary,$i,1); 00065 else 00066 $blong=$blong.substr($binary,$i,1); 00067 00068 } 00069 00070 //now concert to decimal 00071 $lat=$this->binDecode($blat,-90,90); 00072 $long=$this->binDecode($blong,-180,180); 00073 00074 //figure out how precise the bit count makes this calculation 00075 $latErr=$this->calcError(strlen($blat),-90,90); 00076 $longErr=$this->calcError(strlen($blong),-180,180); 00077 00078 //how many decimal places should we use? There's a little art to 00079 //this to ensure I get the same roundings as geohash.org 00080 $latPlaces=max(1, -round(log10($latErr))) - 1; 00081 $longPlaces=max(1, -round(log10($longErr))) - 1; 00082 00083 //round it 00084 $lat=round($lat, $latPlaces); 00085 $long=round($long, $longPlaces); 00086 00087 return array($lat,$long); 00088 } 00089 00090 00094 public function encode($lat,$long) 00095 { 00096 //how many bits does latitude need? 00097 $plat=$this->precision($lat); 00098 $latbits=1; 00099 $err=45; 00100 while($err>$plat) 00101 { 00102 $latbits++; 00103 $err/=2; 00104 } 00105 00106 //how many bits does longitude need? 00107 $plong=$this->precision($long); 00108 $longbits=1; 00109 $err=90; 00110 while($err>$plong) 00111 { 00112 $longbits++; 00113 $err/=2; 00114 } 00115 00116 //bit counts need to be equal 00117 $bits=max($latbits,$longbits); 00118 00119 //as the hash create bits in groups of 5, lets not 00120 //waste any bits - lets bulk it up to a multiple of 5 00121 //and favour the longitude for any odd bits 00122 $longbits=$bits; 00123 $latbits=$bits; 00124 $addlong=1; 00125 while (($longbits+$latbits)%5 != 0) 00126 { 00127 $longbits+=$addlong; 00128 $latbits+=!$addlong; 00129 $addlong=!$addlong; 00130 } 00131 00132 00133 //encode each as binary string 00134 $blat=$this->binEncode($lat,-90,90, $latbits); 00135 $blong=$this->binEncode($long,-180,180,$longbits); 00136 00137 //merge lat and long together 00138 $binary=""; 00139 $uselong=1; 00140 while (strlen($blat)+strlen($blong)) 00141 { 00142 if ($uselong) 00143 { 00144 $binary=$binary.substr($blong,0,1); 00145 $blong=substr($blong,1); 00146 } 00147 else 00148 { 00149 $binary=$binary.substr($blat,0,1); 00150 $blat=substr($blat,1); 00151 } 00152 $uselong=!$uselong; 00153 } 00154 00155 //convert binary string to hash 00156 $hash=""; 00157 for ($i=0; $i<strlen($binary); $i+=5) 00158 { 00159 $n=bindec(substr($binary,$i,5)); 00160 $hash=$hash.$this->coding[$n]; 00161 } 00162 00163 00164 return $hash; 00165 } 00166 00170 private function calcError($bits,$min,$max) 00171 { 00172 $err=($max-$min)/2; 00173 while ($bits--) 00174 $err/=2; 00175 return $err; 00176 } 00177 00178 /* 00179 * returns precision of number 00180 * precision of 42 is 0.5 00181 * precision of 42.4 is 0.05 00182 * precision of 42.41 is 0.005 etc 00183 */ 00184 private function precision($number) 00185 { 00186 $precision=0; 00187 $pt=strpos($number,'.'); 00188 if ($pt!==false) 00189 { 00190 $precision=-(strlen($number)-$pt-1); 00191 } 00192 00193 return pow(10,$precision)/2; 00194 } 00195 00196 00201 private function binEncode($number, $min, $max, $bitcount) 00202 { 00203 if ($bitcount==0) 00204 return ""; 00205 00206 #echo "$bitcount: $min $max<br>"; 00207 00208 //this is our mid point - we will produce a bit to say 00209 //whether $number is above or below this mid point 00210 $mid=($min+$max)/2; 00211 if ($number>$mid) 00212 return "1".$this->binEncode($number, $mid, $max,$bitcount-1); 00213 else 00214 return "0".$this->binEncode($number, $min, $mid,$bitcount-1); 00215 } 00216 00217 00222 private function binDecode($binary, $min, $max) 00223 { 00224 $mid=($min+$max)/2; 00225 00226 if (strlen($binary)==0) 00227 return $mid; 00228 00229 $bit=substr($binary,0,1); 00230 $binary=substr($binary,1); 00231 00232 if ($bit==1) 00233 return $this->binDecode($binary, $mid, $max); 00234 else 00235 return $this->binDecode($binary, $min, $mid); 00236 } 00237 } 00238 00239 00240 00241 00242 00243 00244 ?>
