2010年9月8日水曜日

GeoHex v2 PHP

GeoHexのPHPポーティング (http://github.com/geohex/geohex-php) の
世界対応版(v2.01)で作成しました。
元になるjsがアップデートするとのことですのでまだお試し版です。
問題等ありましたら教えていただけると助かります。

2010/09/11追記
特定の値でloc2zoneがちょっとずれるバグがあるようです。
geohex2_coreのリファクタリングを取り込みつつ調査しています。

<?php
/* GeoHex module for php */
/*  DISCLAIMER OF WARRANTY

BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
NECESSARY SERVICING, REPAIR, OR CORRECTION.

IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.

*/
/*

GeoHex oliginaly written by @sa2da (http://geogames.net/, http://geohex.net/) in Javascript

Ported to PHP by Mage Whopper  at 2010.9.8

Copyright (C) 2009 sa2da (http://twitter.com/sa2da)
License : CC-BY-SA-2.1-JP
  You can find the full legal code at
  http://creativecommons.org/licenses/by/2.1/jp/legalcode (Japanese)
  or in the local file cc-by-sa-2.1-legalcode.html.
  such like http://creativecommons.org/licenses/by-sa/3.0/legalcode (English)
  Here is only an abstract :
 
   You are free:
        to Share – to copy, distribute and transmit the work
        to Remix – to adapt the work

  Under the following conditions:
        Attribution – You must attribute the work in the manner specified by the author or licensor 
             (but not in any way that suggests that they endorse you or your use of the work).
        Share Alike —  If you alter, transform, or build upon this work, you may distribute the
              resulting work only under the same or similar license to this one. 

   For any reuse or distribution, you must make clear to others
        the license terms of this work. The best way to do this
        is with a link to this
        (http://creativecommons.org/licenses/by-sa/3.0/) web page.

  Any of the above conditions can be waived if you get
        permission from the copyright holder.

  Nothing in this license impairs or restricts the author's moral rights.

  SPECIAL THANKS TO @_hfu_ for projection transform mathematical expression
  (http://twitter.com/_hfu_/status/23144088252)
 
*/

class GeoHex{
 var $h_key = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
 var $h_base = 20037508.34;
 var $h_deg;
 var $h_k;
 var $h_size;
 var $h_x;
 var $h_y;
 var $h_lon;
 var $h_lat;

 function __construct(){
  $this->h_deg = pi()*(30/180);
  $this->h_k = tan($this->h_deg);
 }

 // this function only  for downward compatibility
 function geohex2latlng( $code ){
   return $ths->getZoneByCode($code);
 }

 // this function only  for downward compatibility
 function latlng2geohex($lat, $lon, $level){
   return $ths->getZoneByLocation($lat, $lon, $level);
 }

 function setHexSize($_level){
  if($_level < 1 || $_level > 24){
   return -1;
  }
  return $this->h_base/pow(2,$_level)/3;
 }

 function getZoneByLocation( $_lat, $_lon, $_level){
  $this->h_size = $this->setHexSize($_level);
  if($this->h_size < 0){
   die("invalid level error!");
  }
  $level = $_level;

  list($lon_grid, $lat_grid) = $this->loc2xy($_lon,$_lat);
  $unit_x = 6* $this->h_size;
  $unit_y = $unit_x*$this->h_k;
  $h_pos_x = ($lon_grid + $lat_grid/$this->h_k)/$unit_x;
  $h_pos_y = ($lat_grid - $this->h_k*$lon_grid)/$unit_y;
  $h_x_0 = floor($h_pos_x);
  $h_y_0 = floor($h_pos_y);
  $h_x_q = floor(($h_pos_x - $h_x_0)*100)/100;
  $h_y_q = floor(($h_pos_y - $h_y_0)*100)/100;
  $this->h_x = round($h_pos_x);
  $this->h_y = round($h_pos_y);

  $h_max=round($this->h_base/$unit_x + $this->h_base/$unit_y);

  if($h_y_q>-$h_x_q+1){
   if(($h_y_q<2*$h_x_q)&&($h_y_q>0.5*$h_x_q)){
    $this->h_x = $h_x_0 + 1;
    $this->h_y = $h_y_0 + 1;
   }
  }else if($h_y_q<-$h_x_q+1){
   if(($h_y_q>(2*h_x_q)-1)&&($h_y_q<(0.5*$h_x_q)+0.5)){
    $this->h_x = $h_x_0;
    $this->h_y = $h_y_0;
   }
  }
  $this->h_lat = ($this->h_k*$this->h_x*$unit_x + $this->h_y*$unit_y)/2;
  $this->h_lon = ($this->h_lat - $this->h_y*$unit_y)/$this->h_k;

  list($z_loc_x, $z_loc_y) = $this->xy2loc($this->h_lon,$this->h_lat);
  if(($this->h_base - $this->h_lon) < $this->h_size){
   $z_loc_x = 180;
   $h_xy = $this->h_x;
   $this->h_x = $this->h_y;
   $this->h_y = $h_xy;
  }

  $h_x_p =0;
  $h_y_p =0;
  if($this->h_x<0){ $h_x_p = 1;}
  if($this->h_y<0){ $h_y_p = 1;}
  $h_x_abs = abs($this->h_x)*2+$h_x_p;
  $h_y_abs = abs($this->h_y)*2+$h_y_p;
  $h_x_10000 = floor(($h_x_abs%77600000)/1296000);
  $h_x_1000 = floor(($h_x_abs%1296000)/216000);
  $h_x_100 = floor(($h_x_abs%216000)/3600);
  $h_x_10 = floor(($h_x_abs%3600)/60);
  $h_x_1 = floor(($h_x_abs%3600)%60);
  $h_y_10000 = floor(($h_y_abs%77600000)/1296000);
  $h_y_1000 = floor(($h_y_abs%1296000)/216000);
  $h_y_100 = floor(($h_y_abs%216000)/3600);
  $h_y_10 = floor(($h_y_abs%3600)/60);
  $h_y_1 = floor(($h_y_abs%3600)%60);

  $hl_key = $this->h_key;
  $h_code = "" . substr($hl_key, ($level%60), 1);

  if($h_max >=1296000/2) $h_code = $h_code . $hl_key[$h_x_10000] . $hl_key[$h_y_10000];
  if($h_max >=216000/2) $h_code = $h_code . $hl_key[$h_x_1000] . $hl_key[$h_y_1000];
  if($h_max >=3600/2) $h_code = $h_code . $hl_key[$h_x_100] . $hl_key[$h_y_100];
  if($h_max >=60/2) $h_code = $h_code . $hl_key[$h_x_10] . $hl_key[$h_y_10];
  $h_code = $h_code . $hl_key[$h_x_1] . $hl_key[$h_y_1];

  return array(
   "lat"     => $z_loc_y,
   "lon"    => $z_loc_x,
   "x"       => $this->h_x,
   "y"       => $this->h_y,
   "code" => $h_code
  );
 }

 function getZoneByCode($_code){
  $c_length = strlen($_code);
  $zone = array();
  $hl_key=$this->h_key;
  $level = strpos( $hl_key, $_code[0]);
  $scl = $level;
  $this->h_size =  $this->h_base/pow(2,$level)/3;
  $unit_x = 6* $this->h_size;
  $unit_y = 6* $this->h_size* $this->h_k;
  $h_max=round($this->h_base/$unit_x + $this->h_base/$unit_y);
  $this->h_x=0;
  $this->h_y=0;

  if( $h_max >= (1296000/2)) {
   $this->h_x = strpos( $hl_key, $_code[1]) *1296000+strpos( $hl_key, $_code[3] )*216000+strpos( $hl_key,  $_code[5] )*3600+strpos( $hl_key,  $_code[7] )*60+strpos( $hl_key,  $_code[9] );
   $this->h_y = strpos( $hl_key,  $_code[2] )*1296000+strpos( $hl_key,  $_code[4] )*216000+strpos( $hl_key,  $_code[6] )*3600+strpos( $hl_key,  $_code[8] )*60+strpos( $hl_key,  $_code[10] );
  }else if($h_max >=(216000/2) ) {
   $this->h_x = strpos( $hl_key,  $_code[1] )*216000+strpos( $hl_key,  $_code[3] )*3600+strpos( $hl_key,  $_code[5] )*60+strpos( $hl_key,  $_code[7] );
   $this->h_y = strpos( $hl_key,  $_code[2] )*216000+strpos( $hl_key,  $_code[4] )*3600+strpos( $hl_key,  $_code[6] )*60+strpos( $hl_key,  $_code[8] );
  }else if($h_max >=3600/2){
   $this->h_x = strpos( $hl_key,  $_code[1] )*3600+strpos( $hl_key,  $_code[3] )*60+strpos( $hl_key,  $_code[5] );
   $this->h_y = strpos( $hl_key,  $_code[2] )*3600+strpos( $hl_key,  $_code[4] )*60+strpos( $hl_key,  $_code[6] );
  }else if(h_max >=60/2){
   $this->h_x = strpos( $hl_key,  $_code[1] )*60+strpos( $hl_key,  $_code[3] );
   $this->h_y = strpos( $hl_key,  $_code[2] )*60+strpos( $hl_key,  $_code[4] );
  }else{
   $this->h_x = strpos( $hl_key,  $_code[1] );
   $this->h_y = strpos( $hl_key,  $_code[2] );
 }

  $this->h_x = ($this->h_x%2)?-($this->h_x-1)/2:$this->h_x/2;
  $this->h_y = ($this->h_y%2)?-($this->h_y-1)/2:$this->h_y/2;
  $h_lat_y = ($this->h_k*$this->h_x*$unit_x + $this->h_y*$unit_y)/2;
  $h_lon_x = ($h_lat_y - $this->h_y*$unit_y)/$this->h_k;

  list($this->h_lon, $this->h_lat ) = $this->xy2loc($h_lon_x,$h_lat_y);

  return array(
    "code" => $_code,
    "lat"     => $this->h_lat,
    "lon"    => $this->h_lon,
    "x"       => $this->h_x,
    "y"       => $this->h_y
  );
 }

 function loc2xy($_lon, $_lat) {
  $x = $_lon*$this->h_base/180;
  $y = log(tan((90+$_lat)*pi()/360))/(pi()/180);
  $y = $y*$this->h_base/180;
  return array($x, $y);
 }

 function xy2loc($_x, $_y) {
  $lon=($_x/$this->h_base)*180;
  $lat=($_y/$this->h_base)*180;
  $lat=180/pi()*(2*atan(exp($lat*pi()/180))-pi()/2);
  return array($lon, $lat);
 }

 function _gh_strpos( $num ){
  $hl_key = $this->h_key;
  return strpos($hl_key, $_code[$num]);
 }
 
}/* class geohex */

使い方


//モジュールの読み込み
require_once('geohex2.01.php');
 
// GeoHexオブジェクト生成
$ghx = &new GeoHex();
 
// 各計算
// 緯度経度からGeoHexコードを求める
print $ghx->getZoneByLocation($latitude, $longitude, $level) ."\n";
 
// GeoHexコードから緯度経度に変換
print $ghx->getZoneByCode($geohex)."\n";