Skip to content

Commit

Permalink
Merge pull request phayes#39 from mprins/geohash
Browse files Browse the repository at this point in the history
adding GeoHash adapter
  • Loading branch information
phayes committed Jul 10, 2012
2 parents 17301ae + d8a2e6b commit 3a2a914
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 24 deletions.
50 changes: 26 additions & 24 deletions geoPHP.inc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ include_once("lib/adapters/KML.class.php");
include_once("lib/adapters/GPX.class.php");
include_once("lib/adapters/GeoRSS.class.php");
include_once("lib/adapters/GoogleGeocode.class.php");
include_once("lib/adapters/GeoHash.class.php");

// Geometries
include_once("lib/geometry/Geometry.class.php"); // Abtract class
Expand All @@ -32,30 +33,30 @@ include_once("lib/geometry/GeometryCollection.class.php");

class geoPHP
{

static function version() {
return '1.0';
}

// geoPHP::load($data, $type, $other_args);
// if $data is an array, all passed in values will be combined into a single geometry
static function load() {
$args = func_get_args();

$data = array_shift($args);
$type = array_shift($args);

$type_map = geoPHP::getAdapterMap();

$processor_type = $type_map[$type];

if (!$processor_type) {
throw new exception('geoPHP could not find an adapter of type '.htmlentities($type));
exit;
}

$processor = new $processor_type();

// Data is not an array, just pass it normally
if (!is_array($data)) {
$result = call_user_func_array(array($processor, "read"), array_merge(array($data), $args));
Expand All @@ -68,10 +69,10 @@ class geoPHP
}
$result = geoPHP::geometryReduce($geoms);
}

return $result;
}

static function getAdapterMap() {
return array (
'wkt' => 'WKT',
Expand All @@ -83,9 +84,10 @@ class geoPHP
'gpx' => 'GPX',
'georss' => 'GeoRSS',
'google_geocode' => 'GoogleGeocode',
'geohash' => 'GeoHash',
);
}

static function geometryList() {
return array(
'point' => 'Point',
Expand All @@ -97,17 +99,17 @@ class geoPHP
'geometrycollection' => 'GeometryCollection',
);
}

static function geosInstalled($force = NULL) {
static $geos_installed = NULL;
if ($force !== NULL) $geos_installed = $force;
if ($geos_installed !== NULL) {
return $geos_installed;
return $geos_installed;
}
$geos_installed = class_exists('GEOSGeometry');
return $geos_installed;
}

static function geosToGeometry($geos) {
if (!geoPHP::geosInstalled()) {
return NULL;
Expand All @@ -120,7 +122,7 @@ class geoPHP
return $geometry;
}
}

// Reduce a geometry, or an array of geometries, into their 'lowest' available common geometry.
// For example a GeometryCollection of only points will become a MultiPoint
// A multi-point containing a single point will return a point.
Expand All @@ -130,15 +132,15 @@ class geoPHP
if (is_array($geometry)) {
if (count($geometry) == 1) return geoPHP::geometryReduce($geometry[0]);
}

// If the geometry cannot even theoretically be reduced more, then pass it back
if (gettype($geometry) == 'object') {
$passbacks = array('Point','LineString','Polygon');
if (in_array($geometry->geometryType(),$passbacks)) {
return $geometry;
}
}

// If it is a mutlti-geometry, check to see if it just has one member
// If it does, then pass the member, if not, then just pass back the geometry
if (gettype($geometry) == 'object') {
Expand All @@ -153,17 +155,17 @@ class geoPHP
}
}
}
// So now we either have an array of geometries, a GeometryCollection, or an array of GeometryCollections

// So now we either have an array of geometries, a GeometryCollection, or an array of GeometryCollections
if (!is_array($geometry)) {
$geometry = array($geometry);
}

$geometries = array();
$geom_types = array();

$collections = array('MultiPoint','MultiLineString','MultiPolygon','GeometryCollection');

foreach ($geometry as $item) {
if (in_array(get_class($item), $collections)) {
foreach ($item->getComponents() as $component) {
Expand All @@ -176,9 +178,9 @@ class geoPHP
$geom_types[] = $item->geometryType();
}
}

$geom_types = array_unique($geom_types);

if (count($geom_types) == 1) {
if (count($geometries) == 1) {
return $geometries[0];
Expand Down
126 changes: 126 additions & 0 deletions lib/adapters/GeoHash.class.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<?php
/**
* PHP Geometry GeoHash encoder/decoder.
*
* @author prinsmc
* @see http://en.wikipedia.org/wiki/Geohash
*
*/
class GeoHash extends GeoAdapter{
private $table = "0123456789bcdefghjkmnpqrstuvwxyz";

/**
* Convert the geohash to a Point. The point is 2-dimensional.
* @return Point the converted geohash
* @param string $hash a geohash
* @see GeoAdapter::read()
*/
public function read($hash) {
$ll = $this->decode($hash);
return new Point($ll['lon'], $ll['lat'], NULL);
}

/**
* Convert the geometry to geohash.
* @return string the geohash or null when the $geometry is not a Point
* @param Point $geometry
* @see GeoAdapter::write()
*/
public function write(Geometry $geometry){
if($geometry->geometryType()==='Point'){
return $this->encode($geometry);
} else {
return NULL;
}
}

/**
* @return string geohash
* @param Point $point
* @author algorithm based on code by Alexander Songe <[email protected]>
* @see https://github.com/asonge/php-geohash/issues/1
*/
private function encode($point){
$lap = strlen($point->y())-strpos($point->y(),".");
$lop = strlen($point->x())-strpos($point->x(),".");
$precision = pow(10,-max($lap-1,$lop-1,0))/2;

$minlat = -90;
$maxlat = 90;
$minlng = -180;
$maxlng = 180;
$latE = 90;
$lngE = 180;
$i = 0;
$error = 180;
$hash='';
while($error>=$precision) {
$chr = 0;
for($b=4;$b>=0;--$b) {
if((1&$b) == (1&$i)) {
// even char, even bit OR odd char, odd bit...a lng
$next = ($minlng+$maxlng)/2;
if($point->x()>$next) {
$chr |= pow(2,$b);
$minlng = $next;
} else {
$maxlng = $next;
}
$lngE /= 2;
} else {
// odd char, even bit OR even char, odd bit...a lat
$next = ($minlat+$maxlat)/2;
if($point->y()>$next) {
$chr |= pow(2,$b);
$minlat = $next;
} else {
$maxlat = $next;
}
$latE /= 2;
}
}
$hash .= $this->table[$chr];
$i++;
$error = min($latE,$lngE);
}
return $hash;
}

/**
* @param string $hash a geohash
* @author algorithm based on code by Alexander Songe <[email protected]>
* @see https://github.com/asonge/php-geohash/issues/1
*/
private function decode($hash){
$ll = array('lat'=>NULL,'lon'=>NULL);
$minlat = -90;
$maxlat = 90;
$minlng = -180;
$maxlng = 180;
$latE = 90;
$lngE = 180;
for($i=0,$c=strlen($hash);$i<$c;$i++) {
$v = strpos($this->table,$hash[$i]);
if(1&$i) {
if(16&$v)$minlat = ($minlat+$maxlat)/2; else $maxlat = ($minlat+$maxlat)/2;
if(8&$v) $minlng = ($minlng+$maxlng)/2; else $maxlng = ($minlng+$maxlng)/2;
if(4&$v) $minlat = ($minlat+$maxlat)/2; else $maxlat = ($minlat+$maxlat)/2;
if(2&$v) $minlng = ($minlng+$maxlng)/2; else $maxlng = ($minlng+$maxlng)/2;
if(1&$v) $minlat = ($minlat+$maxlat)/2; else $maxlat = ($minlat+$maxlat)/2;
$latE /= 8;
$lngE /= 4;
} else {
if(16&$v)$minlng = ($minlng+$maxlng)/2; else $maxlng = ($minlng+$maxlng)/2;
if(8&$v) $minlat = ($minlat+$maxlat)/2; else $maxlat = ($minlat+$maxlat)/2;
if(4&$v) $minlng = ($minlng+$maxlng)/2; else $maxlng = ($minlng+$maxlng)/2;
if(2&$v) $minlat = ($minlat+$maxlat)/2; else $maxlat = ($minlat+$maxlat)/2;
if(1&$v) $minlng = ($minlng+$maxlng)/2; else $maxlng = ($minlng+$maxlng)/2;
$latE /= 4;
$lngE /= 8;
}
}
$ll['lat'] = round(($minlat+$maxlat)/2, max(1, -round(log10($latE)))-1);
$ll['lon'] = round(($minlng+$maxlng)/2, max(1, -round(log10($lngE)))-1);
return $ll;
}
}

0 comments on commit 3a2a914

Please sign in to comment.