Services_GeoNames
[ class tree: Services_GeoNames ] [ index: Services_GeoNames ] [ all elements ]

Source for file GeoNames.php

Documentation is available at GeoNames.php

  1. <?php
  2.  
  3. /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
  4.  
  5. /**
  6.  * This file is part of the PEAR Services_GeoNames package.
  7.  *
  8.  * PHP version 5
  9.  *
  10.  * LICENSE: This source file is subject to the MIT license that is available
  11.  * through the world-wide-web at the following URI:
  12.  * http://opensource.org/licenses/mit-license.php
  13.  *
  14.  * @category  Services
  15.  * @package   Services_GeoNames
  16.  * @author    David Jean Louis <izi@php.net>
  17.  * @copyright 2008-2009 David Jean Louis
  18.  * @license   http://opensource.org/licenses/mit-license.php MIT License
  19.  * @version   CVS: $Id: GeoNames.php 303916 2010-10-01 10:54:24Z izi $
  20.  * @link      http://pear.php.net/package/Services_GeoNames
  21.  * @link      http://www.geonames.org/export/web-services.html
  22.  * @since     File available since release 0.1.0
  23.  * @filesource
  24.  */
  25.  
  26. /**
  27.  * Dependencies.
  28.  */
  29. require_once 'Services/GeoNames/Exception.php';
  30. require_once 'HTTP/Request2.php';
  31.  
  32. /**
  33.  * Main interface to the GeoNames API:
  34.  * {@link http://www.geonames.org/export/web-services.html}
  35.  *
  36.  * @category  Services
  37.  * @package   Services_GeoNames
  38.  * @author    David Jean Louis <izi@php.net>
  39.  * @copyright 2008-2009 David Jean Louis
  40.  * @license   http://opensource.org/licenses/mit-license.php MIT License
  41.  * @version   Release: 1.0.1
  42.  * @link      http://pear.php.net/package/Services_GeoNames
  43.  * @link      http://www.geonames.org/export/web-services.html
  44.  * @link      http://www.geonames.org/export/ws-overview.html
  45.  * @since     Class available since release 0.1.0
  46.  *
  47.  * @method array    children()                children(array $params)
  48.  * @method array    cities()                  cities(array $params)
  49.  * @method stdclass countryCode()             countryCode(array $params)
  50.  * @method array    countryInfo()             countryInfo(array $params)
  51.  * @method stdclass countrySubdivision()      countrySubdivision(array $params)
  52.  * @method array    earthquakes()             earthquakes(array $params)
  53.  * @method array    findNearby()              findNearby(array $params)
  54.  * @method array    findNearbyPlaceName()     findNearbyPlaceName(array $params)
  55.  * @method array    findNearbyPostalCodes()   findNearbyPostalCodes(array $params)
  56.  * @method array    findNearbyStreets()       findNearbyStreets(array $params)
  57.  * @method stdclass findNearByWeather()       findNearByWeather(array $params)
  58.  * @method array    findNearbyWikipedia()     findNearbyWikipedia(array $params)
  59.  * @method stdclass findNearestAddress()      findNearestAddress(array $params)
  60.  * @method stdclass findNearestIntersection() findNearestIntersection(array $params)
  61.  * @method stdclass get()                     get(array $params)
  62.  * @method stdclass gtopo30()                 gtopo30(array $params)
  63.  * @method array    hierarchy()               hierarchy(array $params)
  64.  * @method stdclass neighbourhood()           neighbourhood(array $params)
  65.  * @method array    neighbours()              neighbours(array $params)
  66.  * @method array    postalCodeCountryInfo()   postalCodeCountryInfo(array $params)
  67.  * @method array    postalCodeLookup()        postalCodeLookup(array $params)
  68.  * @method array    postalCodeSearch()        postalCodeSearch(array $params)
  69.  * @method array    search()                  search(array $params)
  70.  * @method array    siblings()                siblings(array $params)
  71.  * @method array    weather()                 weather(array $params)
  72.  * @method stdclass weatherIcao()             weatherIcao(array $params)
  73.  * @method stdclass srtm3()                   srtm3(array $params)
  74.  * @method stdclass timezone()                timezone(array $params)
  75.  * @method array    wikipediaBoundingBox()    wikipediaBoundingBox(array $params)
  76.  * @method array    wikipediaSearch()         wikipediaSearch(array $params)
  77.  */
  78. {
  79.     // constants {{{
  80.  
  81.     /**
  82.      * Exception code constant defined by this package.
  83.      */
  84.     const UNSUPPORTED_ENDPOINT = 1;
  85.      
  86.     /**#@+
  87.      * Exception codes constants from:
  88.      * {@link http://www.geonames.org/export/webservice-exception.html}
  89.      */
  90.     const AUTHORIZATION_EXCEPTION          = 10;
  91.     const RECORD_DOES_NOT_EXIST            = 11;
  92.     const OTHER_ERROR                      = 12;
  93.     const DATABASE_TIMEOUT                 = 13;
  94.     const INVALID_PARAMETER                = 14;
  95.     const NO_RESULT_FOUND                  = 15;
  96.     const DUPLICATE_EXCEPTION              = 16;
  97.     const POSTAL_CODE_NOT_FOUND            = 17;
  98.     const DAILY_LIMIT_OF_CREDITS_EXCEEDED  = 18;
  99.     const HOURLY_LIMIT_OF_CREDITS_EXCEEDED = 19;
  100.     const WEEKLY_LIMIT_OF_CREDITS_EXCEEDED = 20;
  101.     /**#@-*/
  102.     
  103.     // }}}
  104.     // properties {{{
  105.  
  106.     /**
  107.      * Url of the GeoNames web service.
  108.      *
  109.      * @var string $url 
  110.      */
  111.     public $url = 'http://ws.geonames.org';
  112.  
  113.     /**
  114.      * Array of failover servers.
  115.      *
  116.      * @var array $failoverServers 
  117.      * @see Services_GeoNames::sendRequest()
  118.      */
  119.     public $failoverServers = array();
  120.  
  121.     /**
  122.      * The HTTP_Request2 instance, you can customize the request if you want to
  123.      * (proxy, auth etc...) with the get/setRequest() methods.
  124.      *
  125.      * @var HTTP_Request2 $request 
  126.      * @see Services_GeoNames::getRequest()
  127.      * @see Services_GeoNames::setRequest()
  128.      */
  129.     protected $request;
  130.  
  131.     /**
  132.      * Auth username, only relevant for the geonames commercial web services:
  133.      * {@link http://www.geonames.org/commercial-webservices.html}
  134.      *
  135.      * @var string $username 
  136.      * @see Services_GeoNames::__construct()
  137.      */
  138.     protected $username;
  139.  
  140.     /**
  141.      * Auth token, only relevant for the geonames commercial web services:
  142.      * {@link http://www.geonames.org/commercial-webservices.html}
  143.      *
  144.      * @var string $token 
  145.      * @see Services_GeoNames::__construct()
  146.      */
  147.     protected $token;
  148.  
  149.     /**
  150.      * Array of supported endpoints (listed alphabetically) and their
  151.      * corresponding root property (if any). You can retrieve the list of
  152.      * endpoints (only the keys of this array) with the
  153.      * Services_GeoNames::getSupportedEndpoints() method.
  154.      * 
  155.      * Note that we only support json endpoints, so the following endpoints are
  156.      * not supported:
  157.      * - extendedFindNearby (JSON not available for now)
  158.      * - rssToGeo (RSS/KML only)
  159.      *
  160.      * For a full documentation of the available endpoints services, please
  161.      * see: {@link http://www.geonames.org/export/ws-overview.html}.
  162.      *
  163.      * @var array $endpoints 
  164.      * @see Services_GeoNames::getSupportedEndpoints()
  165.      */
  166.     protected $endpoints = array(
  167.         'children'                => 'geonames',
  168.         'cities'                  => 'geonames',
  169.         'countryCode'             => false,
  170.         'countryInfo'             => 'geonames',
  171.         'countrySubdivision'      => false,
  172.         'earthquakes'             => 'earthquakes',
  173.         'findNearby'              => 'geonames',
  174.         'findNearbyPlaceName'     => 'geonames',
  175.         'findNearbyPostalCodes'   => 'postalCodes',
  176.         'findNearbyStreets'       => 'streetSegment',
  177.         'findNearByWeather'       => 'weatherObservation',
  178.         'findNearbyWikipedia'     => 'geonames',
  179.         'findNearestAddress'      => 'address',
  180.         'findNearestIntersection' => 'intersection',
  181.         'get'                     => false,
  182.         'gtopo30'                 => false,
  183.         'hierarchy'               => 'geonames',
  184.         'neighbourhood'           => 'neighbourhood',
  185.         'neighbours'              => 'geonames',
  186.         'postalCodeCountryInfo'   => 'geonames',
  187.         'postalCodeLookup'        => 'postalcodes'// not a typo
  188.         'postalCodeSearch'        => 'postalCodes',
  189.         'search'                  => 'geonames',
  190.         'siblings'                => 'geonames',
  191.         'weather'                 => 'weatherObservations',
  192.         'weatherIcao'             => 'weatherObservation',
  193.         'srtm3'                   => false,
  194.         'timezone'                => false,
  195.         'wikipediaBoundingBox'    => 'geonames',
  196.         'wikipediaSearch'         => 'geonames',
  197.     );
  198.  
  199.     // }}}
  200.     // __construct() {{{
  201.     
  202.     /**
  203.      * Constructor, if you're using a commercial account (optional), you must
  204.      * pass your "username" and "token".
  205.      * 
  206.      * @param string $username Username for commercial webservice (optional)
  207.      * @param string $token    Token for commercial webservice (optional)
  208.      * 
  209.      * @return void 
  210.      * @access public
  211.      */
  212.     public function __construct($username = null$token = null)
  213.     {
  214.         if ($username !== null{
  215.             $this->username = $username;
  216.         }
  217.         if ($token !== null{
  218.             $this->token = $token;
  219.         }
  220.     }
  221.     
  222.     // }}}
  223.     // __call() {{{
  224.     
  225.     /**
  226.      * Method interceptor that retrieves the corresponding endpoint and return
  227.      * a json decoded object or throw a Services_GeoNames_Exception.
  228.      * 
  229.      * @param string $endpoint The endpoint to call
  230.      * @param array  $params   Array of parameters to pass to the endpoint
  231.      * 
  232.      * @return mixed stdclass|array The JSON decoded response or an array
  233.      * @throws Services_GeoNames_Exception When an invalid method is called or
  234.      *                                      when the websercices returns an error
  235.      */
  236.     public function __call($endpoint$params = array())
  237.     {
  238.         // check that endpoint is supported
  239.         if (!in_array($endpoint$this->getSupportedEndpoints())) {
  240.             throw new Services_GeoNames_Exception(
  241.                 'Unknown service endpoint "' $endpoint '"',
  242.                 self::UNSUPPORTED_ENDPOINT
  243.             );
  244.         }
  245.  
  246.         // handle params
  247.         if (isset($params[0])) {
  248.             $params is_array($params[0]
  249.                 ? $params[0
  250.                 : array('geonameId' => $params[0]);
  251.         else {
  252.             $params = array();
  253.         }
  254.         if (isset($params['type'])) {
  255.             // we only do json
  256.             unset($params['type']);
  257.         }
  258.  
  259.         // manage authentication to commercial webservice
  260.         if ($this->username !== null{
  261.             $params['username'$this->username;
  262.         }
  263.         if ($this->token !== null{
  264.             $params['token'$this->token;
  265.         }
  266.  
  267.         // build the url and retrieve the result
  268.         $qString $this->formatQueryString($params);
  269.         $urlPath '/' $endpoint 'JSON?' $qString;
  270.         $ret     json_decode($this->sendRequest($urlPath));
  271.  
  272.         // check if we have a error response
  273.         if (isset($ret->status->message&& isset($ret->status->value)) {
  274.             throw new Services_GeoNames_Exception(
  275.                 $ret->status->message,
  276.                 (int)$ret->status->value
  277.             );
  278.         }
  279.         
  280.         // remove useless root property, to make the result more user friendly
  281.         if ($this->endpoints[$endpoint!== false && $ret instanceof stdclass{
  282.             $prop $this->endpoints[$endpoint];
  283.             $ret  $ret->$prop;
  284.         }
  285.         return $ret;
  286.     }
  287.     
  288.     // }}}
  289.     // sendRequest() {{{
  290.     
  291.     /**
  292.      * Sends the request to the server using HTTP_Request2.
  293.      * 
  294.      * @param string $urlPath The url path *without* the scheme://host
  295.      * 
  296.      * @return string The response body
  297.      * @throws HTTP_Request2_Exception
  298.      * @throws Services_GeoNames_HTTPException When something goes wrong when
  299.      *                                          building the request or
  300.      *                                          requesting the server.
  301.      */
  302.     protected function sendRequest($urlPath)
  303.     {
  304.         $exceptionStack = array();
  305.         $response       = null;
  306.         array_unshift($this->failoverServers$this->url);
  307.  
  308.         foreach ($this->failoverServers as $server
  309.             try {
  310.                 $request = clone $this->getRequest();
  311.                 $request->setUrl(rtrim($server'/'$urlPath);
  312.                 $response $request->send();
  313.             catch (Exception $exc{
  314.                 $exceptionStack[=  new Services_GeoNames_HTTPException(
  315.                     $exc->getMessage(),
  316.                     $exc
  317.                 );
  318.                 continue;
  319.             }
  320.             if ($response->getStatus(!= 200{
  321.                 $exceptionStack[= new Services_GeoNames_HTTPException(
  322.                     $response->getReasonPhrase(),
  323.                     $response->getStatus(),
  324.                     $response
  325.                 );
  326.                 // reset the response variable since it's not a valid one
  327.                 $response = null;
  328.             else {
  329.                 break;
  330.             }
  331.         }
  332.  
  333.         if ($response == null && !empty($exceptionStack)) {
  334.             $lastException $exceptionStack[count($exceptionStack)-1];
  335.             if (count($exceptionStack== 1{
  336.                 throw $lastException;
  337.             else {
  338.                 throw new Services_GeoNames_HTTPException(
  339.                     $lastException->getMessage(),
  340.                     $exceptionStack
  341.                 );
  342.             }
  343.         }
  344.  
  345.         return $response->getBody();
  346.     }
  347.     
  348.     // }}}
  349.     // formatQueryString() {{{
  350.     
  351.     /**
  352.      * Builds a valid query string (url and utf8 encoded) to pass to the
  353.      * endpoint and returns it.
  354.      * 
  355.      * @param array $params Associative array of query parameters (name=>val)
  356.      * 
  357.      * @return string The formatted query string
  358.      */
  359.     protected function formatQueryString($params = array())
  360.     {
  361.         $qString = array();
  362.         foreach ($params as $name => $value{
  363.             if (is_array($value)) {
  364.                 foreach ($value as $val{
  365.                     $val $this->isUtf8($val$val utf8_encode($val);
  366.                     $qString[$name '=' urlencode($val);
  367.                 }
  368.             else {
  369.                 $value $this->isUtf8($value$value utf8_encode($value);
  370.                 $qString[$name '=' urlencode($value);
  371.             }
  372.         }
  373.         return implode('&'$qString);
  374.     }
  375.     
  376.     // }}}
  377.     // getRequest() {{{
  378.     
  379.     /**
  380.      * Returns the HTTP_Request2 instance, if it's not yet set it is
  381.      * instanciated on the fly.
  382.      * 
  383.      * @return HTTP_Request2 The request
  384.      * @see Services_GeoNames::$request
  385.      */
  386.     public function getRequest()
  387.     {
  388.         if (!$this->request instanceof HTTP_Request2{
  389.             $this->request = new HTTP_Request2();
  390.         }
  391.         return $this->request;
  392.     }
  393.     
  394.     // }}}
  395.     // setRequest() {{{
  396.     
  397.     /**
  398.      * Sets the HTTP_Request2 instance.
  399.      * 
  400.      * @param HTTP_Request2 $request The request to set
  401.      *
  402.      * @return void 
  403.      * @see Services_GeoNames::$request
  404.      */
  405.     public function setRequest(HTTP_Request2 $request)
  406.     {
  407.         $this->request = $request;
  408.     }
  409.     
  410.     // }}}
  411.     // getSupportedEndpoints() {{{
  412.     
  413.     /**
  414.      * Returns an array of supported services endpoints.
  415.      * 
  416.      * @return array The endpoints array
  417.      * @see Services_GeoNames::$endpoints
  418.      */
  419.     public function getSupportedEndpoints()
  420.     {
  421.         return array_keys($this->endpoints);
  422.     }
  423.     
  424.     // }}}
  425.     // isUtf8() {{{
  426.  
  427.     /**
  428.      * Check if the given string is a UTF-8 string or an iso-8859-1 one.
  429.      *
  430.      * @param string $str The string to check
  431.      *
  432.      * @return boolean Wether the string is unicode or not
  433.      */
  434.     protected function isUtf8($str)
  435.     {
  436.         return (bool)preg_match(
  437.             '%^(?:
  438.                   [\x09\x0A\x0D\x20-\x7E]            # ASCII
  439.                 | [\xC2-\xDF][\x80-\xBF]             # non-overlong 2-byte
  440.                 |  \xE0[\xA0-\xBF][\x80-\xBF]        # excluding overlongs
  441.                 | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}  # straight 3-byte
  442.                 |  \xED[\x80-\x9F][\x80-\xBF]        # excluding surrogates
  443.                 |  \xF0[\x90-\xBF][\x80-\xBF]{2}     # planes 1-3
  444.                 | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
  445.                 |  \xF4[\x80-\x8F][\x80-\xBF]{2}     # plane 16
  446.             )*$%xs',
  447.             $str
  448.         );
  449.     }
  450.  
  451.     // }}}
  452. }

Documentation generated on Fri, 01 Oct 2010 11:30:03 +0000 by phpDocumentor 1.4.3. PEAR Logo Copyright © PHP Group 2004.