<?php 
namespace helper;
class Geohash {
    private $coding = "0123456789bcdefghjkmnpqrstuvwxyz";
    private $codingMap = [];
    public $lng=null;
    public $lat=null;
 
    public function Geohash() {
        for($i = 0; $i < 32; $i++) {
            $this->codingMap[substr($this->coding, $i, 1)] = str_pad(decbin($i), 5, "0", STR_PAD_LEFT);
        }
    }
    public function getDistance($lat1, $lng1, $lat2, $lng2){
        $earthRadius = 6367000; //approximate radius of earth in meters 
        $lat1 = ($lat1 * pi() ) / 180; 
        $lng1 = ($lng1 * pi() ) / 180; 
        $lat2 = ($lat2 * pi() ) / 180;
        $lng2 = ($lng2 * pi() ) / 180; 
        $calcLongitude = $lng2 - $lng1; 
        $calcLatitude = $lat2 - $lat1; 
        $stepOne = pow(sin($calcLatitude / 2), 2) + cos($lat1) * cos($lat2) * pow(sin($calcLongitude / 2), 2); 
        $stepTwo = 2 * asin(min(1, sqrt($stepOne))); 
        $calculatedDistance = $earthRadius * $stepTwo;
        return round($calculatedDistance);
    }
    public function decode($hash) {
        $binary = "";
        $hl = strlen($hash);
        for($i = 0; $i < $hl; $i++) {
            $binary .= $this->codingMap[substr($hash, $i, 1)];
        }
 
        $bl = strlen($binary);
        $blat = "";
        $blong = "";
        for ($i = 0; $i < $bl; $i++) {
            if ($i%2) {
                $blat = $blat.substr($binary, $i, 1);
		    } else {
	            $blong = $blong.substr($binary, $i, 1);
		    }
        }
 
        $lat = $this->binDecode($blat, -90, 90);
        $long = $this->binDecode($blong, -180, 180);
 
        $latErr = $this->calcError(strlen($blat), -90, 90);
        $longErr = $this->calcError(strlen($blong), -180, 180);
 
        $latPlaces = max(1, -round(log10($latErr))) - 1;
        $longPlaces = max(1, -round(log10($longErr))) - 1;
 
        $lat = round($lat, $latPlaces);
        $long = round($long, $longPlaces);
 
        return array($lat,$long);
    }
 
    public function encode($lat,$long) {
        $plat = $this->precision($lat);
        $latbits = 1;
        $err = 45;
        while($err > $plat) {
            $latbits++;
            $err/= 2;
        }
 
        $plong = $this->precision($long);
        $longbits = 1;
        $err = 90;
        while($err > $plong) {
            $longbits++;
            $err /= 2;
        }
        $bits = max($latbits,$longbits);
        $longbits = $bits;
        $latbits = $bits;
        $addlong = 1;
        while (($longbits+$latbits) % 5 != 0) {
            $longbits += $addlong;
            $latbits += !$addlong;
            $addlong = !$addlong;
        }
        $blat = $this->binEncode($lat, -90, 90, $latbits);
        $blong = $this->binEncode($long, -180, 180, $longbits);
        $binary = "";
        $uselong = 1;
        while (strlen($blat)+strlen($blong)) {
            if ($uselong) {
                $binary = $binary.substr($blong, 0, 1);
                $blong = substr($blong, 1);
            } else {
                $binary = $binary.substr($blat, 0, 1);
                $blat = substr($blat, 1);
            }
            $uselong = !$uselong;
        }
        $hash = "";
        for ($i = 0; $i < strlen($binary); $i += 5) {
            $n = bindec(substr($binary, $i, 5));
            $hash = $hash . $this->coding[$n];
        }
        return $hash;
    }
 
    private function calcError($bits, $min, $max) {
        $err = ($max - $min) / 2;
        while ($bits--) {
            $err /= 2;
		}
        return $err;
    }
 
    private function precision($number) {
        $precision = 0;
        $pt = strpos($number,'.');
        if ($pt!== false) {
            $precision = -(strlen($number) - $pt - 1);
        }
        return pow(10, $precision) / 2;
    }
 
    private function binEncode($number, $min, $max, $bitcount) {
        if ($bitcount == 0) {
            return "";
		}
        $mid = ($min + $max) / 2;
        if ($number > $mid) {
            return "1" . $this->binEncode($number, $mid, $max, $bitcount - 1);
        } else {
            return "0" . $this->binEncode($number, $min, $mid, $bitcount - 1);
		}
    }
 
    private function binDecode($binary, $min, $max) {
        $mid = ($min + $max) / 2;
 
        if (strlen($binary) == 0) {
            return $mid;
		} 
        $bit = substr($binary, 0, 1);
        $binary = substr($binary, 1);
 
        if ($bit == 1) {
            return $this->binDecode($binary, $mid, $max);
        } else {
            return $this->binDecode($binary, $min, $mid);
        }
    }
}