在PHP中生成geohash长度/精度为4的geohashes列表

时间:2015-11-05 15:48:29

标签: php geolocation geohashing

如果有人能够指引我找到解决问题的方法,我会很感激。

我需要生成地球的所有可能的地理哈希值,精度为4,这样我就可以构建一个查找表来分割位置数据库的数据。

我们计划根据地理哈希的前4个前缀对数据库进行分片,Redis中将有一个密钥存储区,它具有地理哈希前缀及其各自的分片IP。

有些库可以生成哈希并找到邻居但是如何在PHP中生成特定精度的所有可能哈希的列表?

有没有办法使用地理哈希库递归所有邻居并生成列表?我无法弄清楚那个逻辑。

先谢谢。

更新07-11-0215:这是我到目前为止......

<?php
require 'geohash.php';

$hash_column = array();
$hash_list = array();
$center_hash = encode(0,0,3);
array_push($hash_column,$center_hash);
$start_hash = $center_hash;
//generate a list of hashes above '7zz' looping around when reaching top edge.
while(true)
{   
    if(!in_array(adjacent($start_hash,'n'),$hash_column))
    {
        $buffer = adjacent($start_hash,'n');
        array_push($hash_column, $buffer);
    }else
    {
        break;
    }
    $start_hash = $buffer;
}
//iterate through column and get list of hashes to the left of it looping around when reaching edge.
foreach($hash_column as $hash)
{
    array_push($hash_list,$hash);
    while(true)
    {   
        if(!in_array(adjacent($hash,'w'),$hash_list))
        {   
            $buffer = adjacent($hash,'w');
            array_push($hash_list, $buffer);
        }else
        {
            break;
        }
        $hash = $buffer;
    }
}

echo count($hash_list);

geohash.php如下我从Chris Veness的js代码移植到PHP。

<?php

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
/* Geohash encoding/decoding and associated functions        (c) Chris Veness 2014 / MIT Licence  */
/* @author Anand Davis                                                                            */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

/* (Geohash-specific) Base32 map */
$base32Chars = array(
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'b', 'c', 'd', 'e', 'f', 'g',
        'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
    );


/**
 * Encodes latitude/longitude to geohash, either to specified precision or to automatically
 * evaluated precision.
 *
 * @param   {number} lat - Latitude in degrees.
 * @param   {number} lon - Longitude in degrees.
 * @param   {number} [precision] - Number of characters in resulting geohash.
 * @returns {string} Geohash of supplied latitude/longitude.
 *
 * @example
 *     encode(52.205, 0.119, 7);
 */
function encode($lat, $lon, $precision = NULL) {
    // infer precision?
    if (empty($precision)) {
        // refine geohash until it matches precision of supplied lat/lon
        for ($p=1; $p<=12; $p++) {
            $hash = encode($lat, $lon, $p);
            $posn = decode($hash);
            if ($posn[0]==$lat && $posn[1]==$lon) return $hash;
        }
    }

    //$lat = (int)$lat;
    //$lon = (int)$lon;
    //$precision = (int)$precision;

    if (!is_numeric($lat) || !is_numeric($lon) || !is_numeric($precision)) echo 'Invalid geohash';

    $idx = 0; // index into base32 map
    $bit = 0; // each char holds 5 bits
    $evenBit = True;
    $geohash = '';

    $latMin =  -90;
    $latMax =  90;
    $lonMin = -180;
    $lonMax = 180;

    while (strlen($geohash) < $precision) {
        if ($evenBit) {
            // bisect E-W longitude
            $lonMid = ($lonMin + $lonMax) / 2;
            if ($lon > $lonMid) {
                $idx = $idx*2 + 1;
                $lonMin = $lonMid;
            } else {
                $idx = $idx*2;
                $lonMax = $lonMid;
            }
        } else {
            // bisect N-S latitude
            $latMid = ($latMin + $latMax) / 2;
            if ($lat > $latMid) {
                $idx = $idx*2 + 1;
                $latMin = $latMid;
            } else {
                $idx = $idx*2;
                $latMax = $latMid;
            }
        }
        if ($evenBit)
            {
                $evenBit = False;
            }else
            {
                $evenBit = True;
            }

        if ($bit < 4) {
                $bit++;
            } else {
                $geohash .= $GLOBALS['base32Chars'][$idx];
                $bit = 0;
                $idx = 0;
            }

    }

    return $geohash;
}


/**
 * Decode geohash to latitude/longitude (location is approximate centre of geohash cell,
 *     to reasonable precision).
 *
 * @param   {string} geohash - Geohash string to be converted to latitude/longitude.
 * @returns {{{number}lat, {number}lon}} (Center of) geohashed location.
 *
 * @example
 *     decode('u120fxw');
 */
function decode($geohash) {

    $bounds = bounds($geohash); // <-- the hard work
    // now just determine the centre of the cell...

    $latMin = $bounds[0][0]; //sw lat
    $lonMin = $bounds[0][1]; //sw lon
    $latMax = $bounds[1][0]; //ne lat
    $lonMax = $bounds[1][1]; //ne lon

    // cell centre
    $lat = ($latMin + $latMax)/2;
    $lon = ($lonMin + $lonMax)/2;

    // round to close to centre without excessive precision: ⌊2-log10(Δ°)⌋ decimal places
    $lat = round($lat,floor(2-log($latMax-$latMin)/log(10)));
    $lon = round($lon,floor(2-log($lonMax-$lonMin)/log(10)));

    return array( (float)$lat, (float)$lon);
}


/**
 * Returns SW/NE latitude/longitude bounds of specified geohash.
 *
 * @param   {string} geohash - Cell that bounds are required of.
 * @returns {{sw: {lat: number, lon: number}, ne: {lat: number, lon: number}}}
 */
function bounds($geohash) {
    if (strlen($geohash) == 0) echo 'Invalid geohash';

    $geohash = strtolower($geohash);

    $evenBit = True;
    $latMin =  -90;
    $latMax =  90;
    $lonMin = -180; 
    $lonMax = 180;

    for ($i=0; $i<strlen($geohash); $i++) {
        $chr = $geohash[$i];
        $idx = array_search($chr, $GLOBALS['base32Chars']);
        if ($idx == -1) echo 'Invalid geohash';

        for ($n=4; $n>=0; $n--) {
            $bitN = $idx >> $n & 1;
            if ($evenBit) {
                // longitude
                $lonMid = ($lonMin+$lonMax) / 2;
                if ($bitN == 1) {
                    $lonMin = $lonMid;
                } else {
                    $lonMax = $lonMid;
                }
            } else {
                // latitude
                $latMid = ($latMin+$latMax) / 2;
                if ($bitN == 1) {
                    $latMin = $latMid;
                } else {
                    $latMax = $latMid;
                }
            }

            if ($evenBit)
            {
                $evenBit = False;
            }else
            {
                $evenBit = True;
            }
        }
    }

    $bounds = array(
        array($latMin, $lonMin), 
        array($latMax, $lonMax));

    return $bounds;
}

/**
 * Determines adjacent cell in given direction.
 *
 * @param   geohash - Cell to which adjacent cell is required.
 * @param   dirn - Direction (N/S/E/W).
 * @returns {string} Geocode of adjacent cell.
 */
function adjacent($geohash, $dirn) {
    // based on github.com/davetroy/geohash-js

    $geohash = strtolower($geohash);
    $dirn = strtolower($dirn);

    if (strlen($geohash) == 0) echo 'Invalid geohash';
    //if (!('n' === $dirn)) echo 'Invalid direction';

    $neighbour = array(
        'n'=> array('even'=>'p0r21436x8zb9dcf5h7kjnmqesgutwvy', 'odd'=>'bc01fg45238967deuvhjyznpkmstqrwx' ),
        's' => array( 'even'=>'14365h7k9dcfesgujnmqp0r2twvyx8zb', 'odd'=>'238967debc01fg45kmstqrwxuvhjyznp' ),
        'e'=> array('even'=>'bc01fg45238967deuvhjyznpkmstqrwx', 'odd'=>'p0r21436x8zb9dcf5h7kjnmqesgutwvy' ),
        'w'=> array('even'=> '238967debc01fg45kmstqrwxuvhjyznp', 'odd'=>'14365h7k9dcfesgujnmqp0r2twvyx8zb' )
    );
    $border = array(
        'n'=> array( 'even'=>'prxz', 'odd'=>'bcfguvyz' ),
        's'=> array('even'=> '028b',  'odd'=>'0145hjnp' ),
        'e'=> array('even'=>'bcfguvyz', 'odd'=>'prxz'),
        'w'=> array('even'=>'0145hjnp', 'odd'=>'028b')
    );

    $lastCh = substr($geohash, -1);    // last character of hash
    $base = substr($geohash, 0, strlen($geohash) - 1);; // hash without last character
    if(strlen($geohash) % 2 == 0)
    {
        $type = 'even';
    }else
    {
        $type = 'odd';
    }

    // check for edge-cases which don't share common prefix
    if (strpos($border[$dirn][$type],$lastCh) !== False && $base != '') {
        $base = adjacent($base, $dirn);
    }

    // append letter for dirn to parent
    return $base . $GLOBALS['base32Chars'][strpos($neighbour[$dirn][$type],$lastCh)];
}


/**
 * Returns all 8 adjacent cells to specified geohash.
 *
 * @param   {string} geohash - Geohash neighbours are required of.
 * @returns {{n,ne,e,se,s,sw,w,nw: string}}
 */
function neighbours($geohash) {
    return array(
        'n'=>  adjacent($geohash, 'n'),
        'ne'=> adjacent(adjacent($geohash, 'n'), 'e'),
        'e'=> adjacent($geohash, 'e'),
        'se'=> adjacent(adjacent($geohash, 's'), 'e'),
        's'=> adjacent($geohash, 's'),
        'sw'=> adjacent(adjacent($geohash, 's'), 'w'),
        'w'=>  adjacent($geohash, 'w'),
        'nw'=> adjacent(adjacent($geohash, 'n'), 'w')
    );
}

问题是它生成一个包含30504个元素的数组,但是geohash精度为3,它应该有256个(水平散列)* 128个(垂直散列)= 32768个哈希值。

我不知道我哪里出错了?

0 个答案:

没有答案