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

Source for file geo-mashup-geocoders.php

Documentation is available at geo-mashup-geocoders.php

  1. <?php
  2. /**
  3.  * Geo Mashup Geocoder
  4.  *
  5.  * @since 1.4
  6.  * @package GeoMashup
  7.  * @subpackage Geocoder
  8.  */
  9.  
  10.  
  11. if ( ! class_exists( 'GeoMashupHttpGeocoder' ) ) {
  12. /**
  13.  * The Geo Mashup Http Geocoder base class.
  14.  *
  15.  * @since 1.4
  16.  */
  17. abstract class GeoMashupHttpGeocoder {
  18.     /**
  19.      * WP_Http instance
  20.      * @since 1.4
  21.      * @var float 
  22.      */
  23.     protected $http;
  24.     /**
  25.      * Parameters to use with http requests.
  26.      * @var array 
  27.      */
  28.     public $request_params;
  29.     /**
  30.      * Language code.
  31.      * @since 1.4
  32.      * @var string 
  33.      */
  34.     public $language;
  35.     /**
  36.      * Maximum number of results
  37.      * @var int 
  38.      */
  39.     public $max_results;
  40.  
  41.     /**
  42.      * Constructor
  43.      *
  44.      * @param string $args Optional array of arguments:
  45.      *          language - two digit language code, defaults to blog language
  46.      *          max_results - maximum number of results to fetch
  47.      *          default http params - array of WP_Http request parameters, including timeout
  48.      */
  49.     public function  __construct( $args = array() ) {
  50.         $defaults = array(
  51.             'language' => get_locale(),
  52.             'max_results' => 10,
  53.             'request_params' => array( 'timeout' => 3.0 )
  54.         );
  55.         $args = wp_parse_args( $args, $defaults );
  56.         extract( $args );
  57.         $this->language = GeoMashupDB::primary_language_code( $language );
  58.         $this->max_results = absint( $max_results );
  59.         $this->request_params = $request_params;
  60.         if( !class_exists( 'WP_Http' ) )
  61.             include_once( ABSPATH . WPINC. '/class-http.php' );
  62.         $this->http = new WP_Http();
  63.     }
  64.  
  65.     /**
  66.      * Look up locations from a text query, like an address or place name.
  67.      *
  68.      * @since 1.4
  69.      *
  70.      * @param string $query Text query.
  71.      * @return array|WP_ErrorArray of search result locations.
  72.      */
  73.     abstract public function geocode( $query );
  74.  
  75.     /**
  76.      * Look up additional location information from coordinates.
  77.      *
  78.      * Resulting locations have as much address information as
  79.      * possible filled in. Most services return the nearest match
  80.      * first, and some return only one.
  81.      *
  82.      * @since 1.4
  83.      *
  84.      * @param float $lat Latitude
  85.      * @param float $lng Longitude
  86.      * @return array|WP_ErrorArray of search result locations.
  87.      */
  88.     abstract public function reverse_geocode( $lat, $lng );
  89. }
  90.  
  91. /**
  92.  * HTTP geocoder using the geonames web services.
  93.  *
  94.  * Includes an additional method for looking up administrative area names.
  95.  *
  96.  * @since 1.4
  97.  */
  98.     /**
  99.      * The application username to include in geonames API requests.
  100.      * @var string 
  101.      */
  102.     private $geonames_username;
  103.  
  104.     public function  __construct( $args = array() ) {
  105.         global $geo_mashup_options;
  106.         parent::__construct( $args );
  107.         $this->geonames_username = $geo_mashup_options->get( 'overall', 'geonames_username' );
  108.     }
  109.  
  110.     public function geocode( $query ) {
  111.         $url = 'http://api.geonames.org/searchJSON?username=' . $this->geonames_username .
  112.                 '&maxRows=' .  $this->max_results . '&q=' .  urlencode( utf8_encode( $query ) ) .
  113.                 '&lang=' . $this->language;
  114.  
  115.         $response = $this->http->get( $url, $this->request_params );
  116.         if ( is_wp_error( $response ) )
  117.             return $response;
  118.  
  119.         $status = $response['response']['code'];
  120.         if ( '200' != $status ) 
  121.             return new WP_Error( 'geocoder_http_request_failed', $status . ': ' . $response['response']['message'], $response );
  122.  
  123.         $data = json_decode( $response['body'] );
  124.         if ( empty( $data ) or 0 == $data->totalResultsCount ) 
  125.             return array();
  126.  
  127.         $locations = array();
  128.         foreach( $data->geonames as $geoname ) {
  129.             $location = GeoMashupDB::blank_location();
  130.             $location->lat = $geoname->lat;
  131.             $location->lng = $geoname->lng;
  132.             if ( !empty( $geoname->countryCode ) )
  133.                 $location->country_code = $geoname->countryCode;
  134.             if ( !empty( $geoname->adminCode1) )
  135.                 $location->admin_code = $geoname->adminCode1;
  136.             if ( !empty( $geoname->fcode ) and 'PPL' == substr( $geoname->fcode, 0, 3 ) )
  137.                 $location->locality_name = $geoname->name;
  138.             $locations[] = $location;
  139.         }
  140.  
  141.         return $locations;
  142.     }
  143.  
  144.     public function reverse_geocode( $lat, $lng ) {
  145.         global $geo_mashup_options;
  146.  
  147.         if ( !is_numeric( $lat ) or !is_numeric( $lng ) ) // Bad Request
  148.             return new WP_Error( 'bad_reverse_geocode_request', __( 'Reverse geocoding requires numeric coordinates.', 'GeoMashup' ) );
  149.  
  150.         $status = null;
  151.         $url = 'http://api.geonames.org/countrySubdivisionJSON?style=FULL&username=' . $this->geonames_username . 
  152.                 '&lat=' . urlencode( $lat ) .  '&lng=' . urlencode( $lng );
  153.         $response = $this->http->get( $url, $this->request_params );
  154.         if ( is_wp_error( $response ) )
  155.             return $response;
  156.  
  157.         $status = $response['response']['code'];
  158.         $data = json_decode( $response['body'] );
  159.         if ( empty( $data ) or !empty( $data->status ) )
  160.             return array();
  161.         $location = GeoMashupDB::blank_location();
  162.         $location->lat = $lat;
  163.         $location->lng = $lng;
  164.         if ( !empty( $data->countryCode ) )
  165.             $location->country_code = $data->countryCode;
  166.         if ( !empty( $data->adminCode1 ) )
  167.             $location->admin_code = $data->adminCode1;
  168.         
  169.         // Look up more things, postal code, locality or address in US
  170.         if ( 'US' == $location->country_code and GeoMashupDB::are_any_location_fields_empty( $location, array( 'address', 'locality_name', 'postal_code' ) ) ) {
  171.             $url = 'http://api.geonames.org/findNearestAddressJSON?style=FULL&username=' . $this->geonames_username . 
  172.                     '&lat=' . urlencode( $lat ) .  '&lng=' . urlencode( $lat );
  173.             $response = $this->http->get( $url, $this->request_params );
  174.  
  175.             if ( !is_wp_error( $response ) ) {
  176.                 $status = $response['response']['code'];
  177.                 $data = json_decode( $response['body'] );
  178.                 if ( ! empty( $data->address ) ) {
  179.                     $address_parts = array();
  180.                     if ( ! empty( $data->address->street ) ) {
  181.                         $address_parts[] = ( empty( $data->address->streetNumber ) ? '' : $data->address->streetNumber . ' ' ) .
  182.                             $data->address->street;
  183.                     }
  184.                     if ( ! empty( $data->address->adminName1 ) ) {
  185.                         $address_parts[] = $data->address->adminName1;
  186.                     }
  187.                     if ( ! empty( $data->address->postalcode ) ) {
  188.                         $address_parts[] = $data->address->postalcode;
  189.                     }
  190.                     $address_parts[] = $data->address->countryCode;
  191.                     $location->address = implode( ', ', $address_parts );
  192.                     $location->locality_name = $data->address->placename;
  193.                     $location->postal_code = $data->address->postalcode;
  194.                 }
  195.             }
  196.         }
  197.         if (  GeoMashupDB::are_any_location_fields_empty( $location, array( 'address', 'locality_name', 'postal_code' ) ) ) {
  198.             // Just look for a postal code
  199.             $url = 'http://api.geonames.org/findNearbyPostalCodesJSON?username=' . $this->geonames_username . 
  200.                     '&maxRows=1&lat=' . urlencode( $lat ) .  '&lng=' . urlencode( $lng );
  201.             $response = $this->http->get( $url, $this->request_params );
  202.  
  203.             if ( !is_wp_error( $response ) ) {
  204.                 $status = $response['response']['code'];
  205.                 $data = json_decode( $response['body'] );
  206.                 if ( ! empty( $data->postalCodes ) ) {
  207.                     $postal_code = $data->postalCodes[0];
  208.                     $admin_name = ( empty( $postal_code->adminName1 ) ? '' : $postal_code->adminName1 );
  209.                     $location->address = $postal_code->placeName . ', ' .
  210.                         $admin_name . ', ' . $postal_code->postalCode . ', ' .
  211.                         $postal_code->countryCode;
  212.                     $location->locality_name = $postal_code->placeName;
  213.                     $location->postal_code = $postal_code->postalCode;
  214.                 }
  215.             }
  216.         }
  217.         return array( $location );
  218.     }
  219.  
  220.     /**
  221.      * Use the Geonames web service to look up an administrative name.
  222.      *
  223.      * @since 1.4
  224.      *
  225.      * @param string $country_code 
  226.      * @param string $admin_code Admin area name to look up. If empty, look up the country name.
  227.      * @return string|WP_ErrorThe administrative name.
  228.      */
  229.     public function get_administrative_name( $country_code, $admin_code = '' ) {
  230.  
  231.         if ( empty( $admin_code ) ) {
  232.             // Look up a country name
  233.             $country_info_url = 'http://api.geonames.org/countryInfoJSON?username=' . $this->geonames_username .
  234.                     '&country=' . urlencode( $country_code ) .  '&lang=' . urlencode( $this->language );
  235.             $country_info_response = $this->http->get( $country_info_url, $this->request_params );
  236.             if ( is_wp_error( $country_info_response ) )
  237.                 return $country_info_response;
  238.             
  239.             $status = $country_info_response['response']['code'];
  240.             if ( '200' != $status )
  241.                 return new WP_Error( 'geocoder_http_request_failed', $status . ': ' . $country_info_response['response']['message'], $country_info_response );
  242.  
  243.             $data = json_decode( $country_info_response['body'] );
  244.             if ( empty( $data->geonames ) )
  245.                 return '';
  246.             $country_name = $data->geonames[0]->countryName;
  247.             $country_id = $data->geonames[0]->geonameId;
  248.             if ( !empty( $country_name ) ) 
  249.                 GeoMashupDB::cache_administrative_name( $country_code, '', $this->language, $country_name, $country_id );
  250.             return $country_name;
  251.         } else {
  252.             // Look up an admin name
  253.             $admin_search_url = 'http://api.geonames.org/searchJSON?maxRows=1&style=SHORT&featureCode=ADM1&username=' . $this->geonames_username . 
  254.                     '&country=' .  urlencode( $country_code ) . '&adminCode1=' . urlencode( $admin_code );
  255.             $admin_search_response = $this->http->get( $admin_search_url, $this->request_params );
  256.             if ( is_wp_error( $admin_search_response ) )
  257.                 return $admin_search_response;
  258.  
  259.             $status = $admin_search_response['response']['code'];
  260.             if ( '200' != $status )
  261.                 return new WP_Error( 'geocoder_http_request_failed', $status . ': ' . $admin_search_response['response']['message'], $admin_search_response );
  262.  
  263.             $data = json_decode( $admin_search_response['body'] );
  264.             if ( empty( $data ) or 0 == $data->totalResultsCount )
  265.                 return '';
  266.             $admin_name = $data->geonames[0]->name;
  267.             $admin_id = $data->geonames[0]->geonameId;
  268.             if ( !empty( $admin_name ) ) 
  269.                 GeoMashupDB::cache_administrative_name( $country_code, $admin_code, $this->language, $admin_name, $admin_id );
  270.             return $admin_name;
  271.         }
  272.         return '';
  273.     }
  274. }
  275.  
  276. /**
  277.  * HTTP geocoder using the Google geocoding web service
  278.  *
  279.  * @since 1.4
  280.  */
  281.  
  282.     public function  __construct( $args = array() ) {
  283.         parent::__construct( $args );
  284.     }
  285.  
  286.     /**
  287.      * Do regular or reverse geocoding
  288.      *
  289.      * @since 1.4
  290.      *
  291.      * @param string $query_type 'address' or 'latlng'
  292.      * @param string $query 
  293.      * @return array Locations.
  294.      */
  295.     private function query( $query_type, $query ) {
  296.         $google_geocode_url = 'http://maps.google.com/maps/api/geocode/json?sensor=false&' . $query_type . '=' .
  297.             urlencode( utf8_encode( $query ) ) . '&language=' . $this->language;
  298.  
  299.         $response = $this->http->get( $google_geocode_url, $this->request_params );
  300.         if ( is_wp_error( $response ) )
  301.             return $response;
  302.  
  303.         $status = $response['response']['code'];
  304.         if ( '200' != $status )
  305.             return new WP_Error( 'geocoder_http_request_failed', $status . ': ' . $response['response']['message'], $response );
  306.  
  307.         $data = json_decode( $response['body'] );
  308.         if ( 'ZERO_RESULTS' == $data->status )
  309.             return array();
  310.         if ( 'OK' != $data->status ) // status of OVER_QUERY_LIMIT, REQUEST_DENIED, INVALID_REQUEST, etc.
  311.             return new WP_Error( 'geocoder_request_failed', sprintf( __( 'The geocoding request failed with status %s.', 'GeoMashup' ), $status), $data );
  312.  
  313.         if ( count( $data->results ) > $this->max_results )
  314.             $data->results = array_slice( $data->results, 0, $this->max_results );
  315.  
  316.         $locations = array();
  317.         foreach( $data->results as $result ) {
  318.             $location = GeoMashupDB::blank_location();
  319.             $location->lat = $result->geometry->location->lat;
  320.             $location->lng = $result->geometry->location->lng;
  321.             $location->address = $result->formatted_address;
  322.             if ( ! empty( $result->address_components ) ) {
  323.                 foreach( $result->address_components as $component ) {
  324.                     if ( in_array( 'country', $component->types ) )
  325.                         $location->country_code = $component->short_name;
  326.                     if ( in_array( 'administrative_area_level_1', $component->types ) )
  327.                         $location->admin_code = $component->short_name;
  328.                     if ( in_array( 'administrative_area_level_2', $component->types ) )
  329.                         $location->sub_admin_code = $component->short_name;
  330.                     if ( in_array( 'postal_code', $component->types ) )
  331.                         $location->postal_code = $component->short_name;
  332.                     if ( in_array( 'locality', $component->types ) )
  333.                         $location->locality_name = $component->short_name;
  334.                 }
  335.             }
  336.             $locations[] = $location;
  337.         }
  338.         return $locations;
  339.  
  340.     }
  341.  
  342.     public function geocode( $query ) {
  343.         return $this->query( 'address', $query );
  344.     }
  345.  
  346.     public function reverse_geocode( $lat, $lng ) {
  347.  
  348.         if ( !is_numeric( $lat ) or !is_numeric( $lng ) ) // Bad Request
  349.             return new WP_Error( 'bad_reverse_geocode_request', __( 'Reverse geocoding requires numeric coordinates.', 'GeoMashup' ) );
  350.  
  351.         return $this->query( 'latlng', $lat . ',' . $lng );
  352.     }
  353. }
  354.  
  355. /**
  356.  * HTTP geocoder using the nominatim web service.
  357.  *
  358.  * @since 1.4
  359.  */
  360.  
  361.     public function  __construct( $args = array() ) {
  362.         parent::__construct( $args );
  363.     }
  364.  
  365.     public function geocode( $query ) {
  366.  
  367.         $geocode_url = 'http://nominatim.openstreetmap.org/search?format=json&polygon=0&addressdetails=1&q=' .
  368.             urlencode( utf8_encode( $query ) ) . '&accept-language=' . $this->language .
  369.             '&email=' . urlencode( get_option( 'admin_email' ) );
  370.  
  371.         $response = $this->http->get( $geocode_url, $this->request_params );
  372.         if ( is_wp_error( $response ) )
  373.             return $response;
  374.  
  375.         $status = $response['response']['code'];
  376.         if ( '200' != $status )
  377.             return new WP_Error( 'geocoder_http_request_failed', $status . ': ' . $response['response']['message'], $response );
  378.  
  379.         $data = json_decode( $response['body'] );
  380.         if ( empty( $data ) )
  381.             return array();
  382.  
  383.         if ( count( $data ) > $this->max_results )
  384.             $data = array_slice( $data, 0, $this->max_results );
  385.  
  386.         $locations = array();
  387.         foreach( $data as $result ) {
  388.             $location = GeoMashupDB::blank_location();
  389.             $location->lat = $result->lat;
  390.             $location->lng = $result->lon;
  391.             $location->address = $result->display_name;
  392.             if ( !empty( $result->address ) ) {
  393.                 if ( !empty( $first_result->address->country_code ) )
  394.                     $location->country_code = strtoupper( $first_result->address->country_code );
  395.                 // Returns admin name in address->state, but no code
  396.                 if ( !empty( $first_result->address->county ) )
  397.                     $location->sub_admin_code = $first_result->address->county;
  398.                 if ( !empty( $first_result->address->postcode ) )
  399.                     $location->postal_code = $first_result->address->postcode;
  400.                 if ( !empty( $first_result->address->city ) )
  401.                     $location = $first_result->address->city;
  402.             }
  403.             $locations[] = $location;
  404.         }
  405.         return $locations;
  406.     }
  407.  
  408.     public function reverse_geocode( $lat, $lng ) {
  409.  
  410.         if ( !is_numeric( $lat ) or !is_numeric( $lng ) ) // Bad Request
  411.             return new WP_Error( 'bad_reverse_geocode_request', __( 'Reverse geocoding requires numeric coordinates.', 'GeoMashup' ) );
  412.  
  413.         $geocode_url = 'http://nominatim.openstreetmap.org/reverse?format=json&zoom=18&address_details=1&lat=' .
  414.             $lat . '&lon=' . $lng . '&email=' . urlencode( get_option( 'admin_email' ) );
  415.  
  416.         $response = $this->http->get( $geocode_url, $this->request_params );
  417.         if ( is_wp_error( $response ) )
  418.             return $response;
  419.  
  420.         $status = $response['response']['code'];
  421.         if ( '200' != $status )
  422.             return new WP_Error( 'geocoder_http_request_failed', $status . ': ' . $response['response']['message'], $response );
  423.  
  424.         $data = json_decode( $response['body'] );
  425.         if ( empty( $data ) )
  426.             return array();
  427.  
  428.         $location = GeoMashupDB::blank_location();
  429.         $location->lat = $lat;
  430.         $location->lng = $lng;
  431.         $location->address = $data->display_name;
  432.         if ( !empty( $data->address ) ) {
  433.             if ( !empty( $data->address->country_code ) )
  434.                 $location->country_code = strtoupper( $data->address->country_code );
  435.             // Returns admin name in address->state, but no code
  436.             if ( !empty( $data->address->county ) )
  437.                 $location->sub_admin_code = $data->address->county;
  438.             if ( !empty( $data->address->postcode ) )
  439.                 $location->postal_code = $data->address->postcode;
  440.             if ( !empty( $data->address->city ) )
  441.                 $location->locality_name = $data->address->city;
  442.         }
  443.         return array( $location );
  444.     }
  445. }
  446.  
  447. }

Documentation generated on Sat, 09 Jul 2011 21:54:42 -0700 by phpDocumentor 1.4.3