Geocoding with Google Maps and the Zend Framework

Geocoding with Google Maps and the Zend Framework

Download Example Files :

Zip RAR


Maps are a great way to engage your users and visualize data, but it can be a little tricky to setup. Here I will walk you through the steps needed to get a map up and running.

Sign Up

Apply for a Google Maps API Key (free) here using your domain without a subdomain or www (this will allow you to use key with or without www and on subdomains): http://code.google.com/apis/maps/signup.html

Configuration

Put the API key in your configuration file (application.ini, config.ini, etc.) like this:

[google]
google_map_key =  YOUR_KEY

Bootstrapping

In your index.php file, add the following code:
As a concerned user pointed out, that is terrible practice and was a remnant of my first tinkerings with ZF. You should place this code in your Bootstrap.php file:

protected function initGoogle(){
  defined('CONFIG_PATH') or define('CONFIG_PATH', APPLICATION_PATH . '/configs/application.ini');
  Zend_Registry::set('google',  new Zend_Config_Ini(CONFIG_PATH, 'google'));
}

This will add your api key to the registry so that it can be accessed throughout your application.

Model

In your models directory, create a new class called ‘Geocoder.php’. This will be the file that contains the application logic for retreiving coordinates on a given location.

models/Geocoder.php

class Geocoder{
    protected $key;

    public function __construct($api_key)
    {
        $this->key = $api_key;
    }

    public function _getGeocodedLatitudeAndLongitude($address)
    {
        $client = new Zend_Http_Client();
        $client->setUri($this->getGeocodingUri());
        $client->setParameterGet('q', urlencode($address))
               ->setParameterGet('output', 'json')
               ->setParameterGet('sensor', 'false')
               ->setParameterGet('key', $this->key);

        $result = $client->request('GET');
        $response = Zend_Json_Decoder::decode($result->getBody(),
                    Zend_Json::TYPE_OBJECT);
        return $response;
    }

    public function getCoordinates($address)
    {
        $response = $this->_getGeocodedLatitudeAndLongitude($address);
        if(isset($response->Placemark[0]->Point->coordinates[1])){
             return array(
                'lat' => $response->Placemark[0]->Point->coordinates[1],
                'lon' => $response->Placemark[0]->Point->coordinates[0]
            );
        } else {
      return null;
    }
    }

    private function getGeocodingUri()
    {
        return 'http://maps.google.com/maps/geo';
    }
}
?>

Usage

Alright, that was pretty easy. Now we just have to implement this in our controller. Here is a sample model that uses the Geocoder class to return geotagged User objects.
models/User.php

class User extends Zend_Db_Table_Abstract{
  protected $_name = 'users';
  protected $key;
  protected $geocoder;

  public function init()
  {
    $this->key = Zend_Registry::get('google')->google_map_key;
    $this->geocoder = new Geocoder($this->key);

  }
  public function getUsersAndGeocode()
  {
    $result = $this->fetchAll();

    $users    = $result->_toArray();
    foreach($users as $user)
    {
      $address = "{$user['address']} {$user['city']} {$user['state']} {$user['zip']}";

      $latlon = $this->geocoder->getCoordinates($address);
      if($latlon)
      {
        $user['lat'] = $latlon['lat'];
        $user['lon'] = $latlon['lon'];
      }
    }
    return $users;
  }

}
?>

The controller part is a little too easy. We will setup a reference to our user table and create a map action. Within the map action we get all of users and geocode them. We pass that collection to our view. This file should be located here:
controllers/UserController.php

class UserController extends Zend_Controller_Action{
  protected $user_table;
  public function init()
  {
    //database connectivity stuff is up to you

    $this->user_table = new User();
  }
  public function mapAction()
  {
    $this->view->api_key = Zend_Registry::get('google')->google_map_key;
    $users = $this->user_table->getUsersAndGeocode();
    $this->view->users = $users;
  }
}
?>

And finally, the view. Here we will create our map div, load the Google Maps API (don’t forget to sub out your key in the script src). We are using a custom marker here (personIcon); you can add your own image here by changing the path to your image. I recommend the image be pretty small with a transparent background. Given our current setup, this file would be located at this path:
views/scripts/user/map.phtml

<div id="map">

<noscript>
<center>
<strong>To view the map feature, you need to have Javascript enabled. If you are unsure about how to turn Javascript support on, please</p>
<p>read Google's instructions found here: <a href="; target="_blank">LINK</a></strong>
</center></p>
<p></noscript>
</div>
<script type="text/javascript" src=";
<script type="text/javascript" src=";?=$this->api_key?>"></script>

<script type="text/javascript">
 //Sorry, I love jquery! If you don't use window.onload = function(){initialize();}
    $(document).ready(function(){
        initialize();
    });
var map;
var personIcon;
function initialize() {
    if(GBrowserIsCompatible){

        map = new google.maps.Map2(document.getElementById("map"));
        map.setUIToDefault();
        map.setCenter(new google.maps.LatLng('48.858001709', '2.29460000992'), 4);
    
        personIcon            = new GIcon();
    
    /**
    * Change this marker to be a small transparent custom image
    *
    */
        personIcon.image      = '/img/common/custom_marker.png';
        personIcon.shadow     = ";;
        personIcon.iconSize   = new GSize(32,32);
        personIcon.shadowSize = new GSize(37,34);

        personIcon.iconAnchor = new GPoint(9,34);
        personIcon.infoWindowAnchor = new GPoint(9,2);
    }
    addOverlays(map);
}

function personMarker(point, index, dto){
 //set our marker to be the custom icon we created
    markerOptions = {icon:personIcon};
    var marker = new GMarker(point, markerOptions);

/**
 * This is the content in the info bubble
 */
    GEvent.addListener(marker, 'click', function(){
       marker.openInfoWindowHtml(
            '<h3><a href="/profile/view/'+dto.user_id+'">'+dto.user_first+' '+ dto.user_last+'</a></h3>'+
            dto.user_address+' '+dto.user_city+' '+dto.user_state+' '+dto.user_zip
        );

    });
    return marker;
}
/*
* Here is where we loop through our users and create new markers for each

*/
function addOverlays(map){
    <?php
  $geo = $this->users->_toArray();
        $size = sizeof($geo);

 ?>
    <? for($i = 0; $i < $size; $i++): ?>
                var latLon = new GLatLng(<?php echo $geo[$i]['lat'];?>, <?php echo $geo[$i]['lon'];?>);

                map.addOverlay(personMarker(latLon, <?=$i?>, <?=Zend_Json::encode($geo[$i])?>));
    <?php endfor; ?>
}
</script>

Post a comment if you have any questions


About the Author

Rob McVey

I am a software developer/IT professional helping businesses save money through informed purchase consulting; website development and marketing; and process automation.

8 Comments to “Geocoding with Google Maps and the Zend Framework”

  1. Jim Altoff says:

    Thanks for this! I was trying to figure out how to work the Google Maps API into a Zend project and was not having much luck. The custom icon is cool too!

    Thanks again!

    -Jim

  2. says:

    Another way to do this is to store your key in the config then just load the key out of the config via your bootstrap and set it in the registry.

    Then create a Service class in your custom library. and use Zend_Http

    $this->_httpClient->setUri("http://maps.google.com/maps/geo");
    $this->_httpClient->setHeaders('Accept-Charset', 'ISO-8859-1,utf-8');
    $this->_httpClient->setParameterGet(array(
    'key' => $this->_key,
    'output' => $this->_output,
    'q' => $address,
    ));
    $data = $this->_httpClient->request('GET');
    return $data->getBody();

    An adapter seems like a lot of work to get a lat long.

    • Rob says:

      Hi Shaun, thanks for the input. You are correct, an adapter would be overkill. I am not quite sure why I put “Adapter” in there, as it is really meant to be used as a model, the references have been fixed to be Geocoder.

      If I’m not mistaken, the only change you’ve proposed is moving the code to a “Service” class, as I am already making use of Zend_Http in pretty much the exact same way. By having this logic in a model, I am able to have utility methods and expose more methods without fudging the purpose of the class. In reality, it makes the most sense to incorporate both approaches, and retrieve the data for my Geocoder model from a Services class.

      It’s not about the amount of work I need to do get lat and lon, I can reuse this class as many times as I want. It is more about providing a clean and intuitive interface to gathering geodata.

      Cheers

  3. Hi Rob,

    Why are you using index.php tot put the key to the registry ?
    Isn’t it better to for example use the bootstrap file ?
    Like this for example ?

    protected function _initGoogleMapsKey()
    {
    defined(‘CONFIG_PATH’)
    or define(‘CONFIG_PATH’, APPLICATION_PATH . ‘/configs/application.ini’);
    Zend_Registry::set(‘google’, new Zend_Config_Ini(CONFIG_PATH, ‘google’));
    }

    I think this is best practice to configure the google maps key, as normally you do not touch index.php at all, or you should want to use another google framework version, which you can config in the index.php.

    Best Regards,

    Patrick

  4. Your public function getCoordinates seems not to be correct…

    $return(
    ‘lat’ => $result->Placemark[0]->Point->coordinates[1];
    ‘lon’ => $result->Placemark[0]->Point->coordinates[0];
    );

    is no valid php code… so i guess that has to be…

    $return = array(
    ‘lat’ => $result->Placemark[0]->Point->coordinates[1],
    ‘lon’ => $result->Placemark[0]->Point->coordinates[0]
    );

    If that is correct you can also avoid the $return = array(); a few lines above, as $return is always initialised in the in the if-then construction.

    Best Regards,

    Patrick

    • Rob says:

      Hello again, Patrick,

      I don’t know if I updated and forgot since I read this comment last but that return statement is fixed. The syntaxhighlighting plugin I have been using keeps messing up my code and escaping the HTML characters. I am pretty sure all of the places that the code was being escaped have been fixed.

  5. Canli Yayin says:

    One of my best articles i have read at last days. thank you dude….