As many of you know, I am apart of the team which has developed and deployed Emitter.ca. What many don’t know is that Emitter.ca has leveraged both open source technologies like PHP with a combination of Microsoft’s proprietary platforms like SQL Server, Windows Server 2008 and also Bing Maps API.

If you haven’t tried Emitter.ca, go do so now – www.emitter.ca. However, if you do not know of a Canadian address or search terms to use – feel free to use one of the following postal codes: N6C-3Z4 or S9V-1Y2.

One of the key features which was required for Emitter.ca, was the ability to geocode search terms like street addresses, cities, and postal codes, that a user enters so that Emitter.ca could easily return facilities that are nearby. To do this, we leveraged Bing Maps’s API for performing the Geocode lookup. Microsoft offers a variety of APIs for allowing your application to interact with Bing Maps, however, for Geocoding search terms, we utilized Bing’s SOAP API.

Using the SOAP API, there were a couple of things that we needed to do before hand. First, we needed to request an API key so that we can appropriately interact with the web service. By following the steps on “Getting a Bing Maps Key“, I had achieved this.

Secondly, after some extensive look over of the API documentation, I realized that there had to be a more appropriate way to perform the API calls and not have to handle the parsing of data throughout the business logic, but instead, in a library class. Therefore, that is what I did. The code below is what was developed for the needs of the application.

/**
 * BingMapsGeocodeSoapService
 */
class BingMapsGeocodeSoapService {
    /* Member variables */
    const API_KEY       = "";
    const GEOCODE_WSDL  = "http://dev.virtualearth.net/webservices/v1/metadata/geocodeservice/geocodeservice.wsdl";

    /**
     * __construct
     *
     * Ctor.
     *
     * @access:
     * @param:
     * @return:
     */
    public function __construct() {
        /* void */
    }

    /**
     * geocode_lookup_raw
     *
     * Performs a standard geocode lookup using the query, returns the response that is
     * returned from performing the geocode lookup.
     *
     * @access: public
     * @param:  string
     * @return: mixed       Returns false on failure, else returns an object.
     */
    public function geocode_lookup_raw($query) {
        return $this->_geocode_lookup($query);
    }

    /**
     * geocode_lookup
     *
     * Performs a standard geocode lookup using the query, returns an array
     * of objects containing only latitudes, longitudes, formatted query and the locality.
     *
     * @access: public
     * @param:  string
     * @return: mixed       Returns false on failure, else returns array
     */
    public function geocode_lookup($query) {
        $geocode_results = $this->_geocode_lookup($query);
        if( $geocode_results ) {
            try {
                $geocode_results = &$geocode_results->GeocodeResult->Results;

                if( is_array($geocode_results->GeocodeResult) ) {
                    $results = array();
                    foreach( $geocode_results->GeocodeResult as &$result ) {
                        $o = new stdClass;
                        $o->formatted_address = $result->Address->FormattedAddress;
                        $o->locality = $result->Address->Locality;

                        if( is_array($result->Locations->GeocodeLocation) ) {
                            $o->latitude = $result->Locations->GeocodeLocation[0]->Latitude;
                            $o->longitude = $result->Locations->GeocodeLocation[0]->Longitude;
                        } else {
                            $o->latitude = $result->Locations->GeocodeLocation->Latitude;
                            $o->longitude = $result->Locations->GeocodeLocation->Longitude;
                        }

                        $results[] = $o;
                    }

                    return $results;

                } else {
                    if( isset($geocode_results->GeocodeResult->Locations->GeocodeLocation) ) {
						$o = new stdClass;
						$o->formatted_address = $geocode_results->GeocodeResult->Address->FormattedAddress;
						$o->locality = $geocode_results->GeocodeResult->Address->Locality;

						if( is_array($geocode_results->GeocodeResult->Locations->GeocodeLocation) ) {
							$o->latitude = $geocode_results->GeocodeResult->Locations->GeocodeLocation[0]->Latitude;
							$o->longitude = $geocode_results->GeocodeResult->Locations->GeocodeLocation[0]->Longitude;
						} else {
							$o->latitude = $geocode_results->GeocodeResult->Locations->GeocodeLocation->Latitude;
							$o->longitude = $geocode_results->GeocodeResult->Locations->GeocodeLocation->Longitude;
						}

                        return array($o);
                    }

                    return false;
                }
            }
            catch( Exception $e ) {
                // throw $e;
                return false;
            }
        }

        return false;
    }

    /**
     * _geocode_lookup
     *
     * @access: protected
     * @param:  string
     * @return: mixed
     */
    final protected function _geocode_lookup($query) {
        $query = trim($query);
        if( $query ) {
            try {
                $soap_client = new SoapClient(self::GEOCODE_WSDL);
                $geocode_response = $soap_client->Geocode(array(
                    'request' => array(
                        'Credentials' => array('ApplicationId' => self::API_KEY),
                        'Query' => $query
                     )
                ));

                return $geocode_response;
            }
            catch( Exception $e ) {
                throw new Exception("An error has occurred with the SOAP client request.", 0, $e);
            }
        }

        return false;
    }
}

As you can see, this class contains two properties which are constants. One holds the web service URL, and the other which holds the API key which was created earlier when we followed the Getting a Bing Maps Key how to.

By building this simple class to handle the communication between Bing Maps API and Emitter.ca, it made my life a lot easier when working with the web service, and it also assists in code maintenance. Now, I can simply create an instance of this class and call a single method which returns only the locality, the formatted address, the latitude and longitude for the search terms. Here is an example:

$geocode_webservice = new BingMapsGeocodeSoapService();
$results = $geocode_webservice->geocode_lookup("Toronto, Ontario");
if( $results ) {
     // search terms have been geocoded, now let's print them out
     print_r( $results );
} else {
     // an error has occurred, search terms could not be geocoded.
}

What is most interesting about this class is that when you invoke the geocode_lookup() method, instead of just returning a single set of results, the method will actually return an array of results. This assists in helping handle ambiguous search terms. It also allows the application to determine which set of data it should use for continuing its process, if any, or simply just providing an interface to allow the user to decide which search term they actually mean (using the formatted address).

If you plan on using this simple wrapper class for Bing’s Geocode SOAP service, please remember to set you API key to the value of the member constant named API_KEY.

Please note, that this source code is licensed under the GPL version 2.