Mapping with Geocoder PHP and Leaflet.js

Fredric Mitchell
Tweet

Interactive maps inside a web application have a lot of great uses. From visualizing data to highlighting points of interest, maps are expected to communicate ideas within the context of location easily.

The hardest part, however, is converting that data into coordinates that the map can understand. Luckily, Geocoder PHP allows us to connect to different geo-coding providers. Combined with Leaflet.js, a simple Javascript library, creating maps is a breeze.

Starting

With Composer, including the Geocoder PHP library is simple:

{
  "require": {
    "willdurand/geocoder": "*"
  }
}

Let's also add some html to a simple index.php file to include Bootstrap so that we have a nice-looking environment to display our map in:

<?php
require 'vendor/autoload.php';

?>
<!DOCTYPE html>
<html>
<head>
    <title>A simple map with Geocoder PHP and Leaflet.js</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-lg-12 page-header">
            <h1 id="header">A simple map with Geocoder PHP and Leaflet.js</h1>
        </div>
        <div class="row-fluid">
            <div class="col-lg-8">

            </div>
        </div>
    </div><!-- /row -->
</div> <!-- /container -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
</body>
</html>

Setting Up Geocoder

Geocoder bills itself as the missing the geocoder library for PHP. It can be used in three simple steps:

  1. Register an adapter
  2. Register a provider
  3. Geocode!

Register an Adapter

The adapter serves as the mechanism to connect and get data to your chosen provider via their API. Some adapters use the built-in functionality within PHP 5.3+, like cURL and sockets. Others, like Buzz, Guzzle, and Zend HTTP Client, use third-party open source libraries that simply require you to add their dependency to your composer file.

The beauty of the Geocoder library is this abstraction of the adapter step. It allows you swap out your adapter if your needs change without requiring you to rewrite how your application receives the data.

For this example, we're going to use cURL and the included CurlHTTPAdapter class inside the Geocoder PHP library.

In our index.php file, let's add the adapter:

// Setup geocoder adapter.
$adapter = new \Geocoder\HttpAdapter\CurlHttpAdapter();

Register a provider

There are many geocoding providers that are supported out-of-the-box by the Geocoder PHP library, including Google Maps, Bing Maps, Nominatim via Openstreetmap, and TomTom.

The full list can be found on the README of the Geocoder PHP repository.

Each provider, as represented by its respective classes, has its own options. Depending on your needs, you can register multiple providers for various circumstances. This may be useful if your application needs to map specific streets in San Jose, Costa Rica using Openstreetmap and find a quick route in Beijing, China using Baidu.

For this example, I'll simply use Google Maps, but register it in a way that if I want to add another provider in the future, I simply need to add it to an array:

// Create a chain of providers.
// Be sure to include my previously created adapter.
$chain = new \Geocoder\Provider\ChainProvider(
    array(
        new \Geocoder\Provider\GoogleMapsProvider($adapter),
    )
);

// Instantiate the geocoder.
$geocoder = new \Geocoder\Geocoder();

// Register my providers.
$geocoder->registerProvider($chain);

Geocode

We can now pass the address to the geocode() method inside your newly instantiated $geocoder object. This will return a result object that is instantiated through the provider chosen earlier. This result object has getLatitude() and getLongitude() methods we can use.

// Demo locations
$locations = array(
    array(
        'address' => '3324 N California Ave, Chicago, IL, 60618',
        'title' => 'Hot Dougs',
    ),
    array(
        'address' => '11 S White, Frankfort, IL, 60423',
        'title' => 'Museum',
    ),
    array(
        'address' => '1000 Sterling Ave, , Flossmoor, IL, 60422',
        'title' => 'Library',
    ),
    array(
        'address' => '2053 Ridge Rd, Homewood, IL, 60430',
        'title' => 'Twisted Q',
    )
);

foreach ($locations as $key => $value) {
    // Try to geocode.
    try {
        $geocode = $geocoder->geocode($value['address']);
        $longitude = $geocode->getLongitude();
        $latitude = $geocode->getLatitude();

    } catch (Exception $e) {
        echo $e->getMessage();
    }
}

Integrating with Leaflet.js

Leaflet.js is a powerful javascript library that makes mapping very easy.

Maps consist of three parts:

  1. Tiles
  2. Interaction layer (typically through Javascript and CSS)
  3. Data points

The tiles are the 256 by 256 pixel squares that show the map detail. Services like Mapbox and Cloudmade allow you to create your own tilesets. For this example, I've created a free account with Cloudemade and will use the API key given to show tiles from their hosting service.

The interaction layer is handled by Leaflet.js. I simply include the Leaflet Javascript and CSS library into our starter HTML template:

<link rel="stylesheet" href="//cdn.leafletjs.com/leaflet-0.6.4/leaflet.css" />
<script src="//cdn.leafletjs.com/leaflet-0.6.4/leaflet.js"></script>

The data points have been created earlier with my geocoder code. I simply have to format the array of data in a way Leaflet expects.

In this simple example, I'm simply going to create individual markers as Javascript variables that will be included in my map via strings produced by PHP.

Leaflet has the option for this data to also be passed in via the geoJSON format for larger and more dynamic datasets.

$mapdata = $marker_group = array();

foreach ($locations as $key => $value) {
    // Try to geocode.
    try {
        $geocode = $geocoder->geocode($value['address']);
        $longitude = $geocode->getLongitude();
        $latitude = $geocode->getLatitude();

        // Create map data array
        $mapdata[] = markerCreator($latitude, $longitude, $value['title'], $key);

        // Marker grouping array
        $marker_group[] = "marker{$key}";

    } catch (Exception $e) {
        echo $e->getMessage();
    }
}

function markerCreator($lat, $long, $label, $key) {
    return "var marker{$key} = L.marker([{$lat}, {$long}]).addTo(map);
    marker{$key}.bindPopup(\"{$label}\");";
}

Because Leaflet injects the map code into an existing DOM element, we first have to define that element inside our HTML. We can do this by simply creating a div with a unique id:

<div id="map"></div>

We can then target that id in Leaflet by calling the built-in map() Javascript method and pass in our id in the footer:

var map = L.map('map');

Now, we build the three parts of our map. To register the tiles, we simply call the built-in tileLayer() method, defining attributes and zoom level if desired, then appending the addTo() method:

L.tileLayer('//{s}.tile.cloudmade.com/41339be4c5064686b781a5a00678de62/998/256/{z}/{x}/{y}.png', {maxZoom: 18}).addTo(map);

Finally, we print our map data using the PHP array we defined earlier, and make sure the map centers itself on those data points by defining them together as group. All in all, the Javascript in the footer would be:

<script>
    var map = L.map('map');

    L.tileLayer('//{s}.tile.cloudmade.com/41339be4c5064686b781a5a00678de62/998/256/{z}/{x}/{y}.png', {maxZoom: 18}).addTo(map);

    <?php print implode('', $mapdata); ?>

    var group = new L.featureGroup([<?php print implode(', ', $marker_group); ?>]);
    map.fitBounds(group.getBounds());
</script>

If you've gotten this far, you'll notice that nothing happens.

This is because Leaflet does not inject properties on the height or width of the map div, allowing you to style it and resize it as you please. Simply give your div a height and width, and your map should appear!

Conclusion

You can create beautiful, interactive maps with the Geocoder PHP library and Leaflet.js. Be sure to check out the respective documentation of each project as there are many more advanced customizations that are possible.

Check out the demo for this article or fork it over at Github.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Anonymous

    If rate limits are an issue, a possible alternative is to use client-side geocoding to distribute the requests among your users.

    Checkout Geocoder JS if needing to go down that route – https://github.com/geocoder-php/geocoder-js

  • Anonymous

    Great Article. Integration with Mapping is going to be very useful on an upcoming project and you just saved me the research of the libraries and techniques!

  • Fezfez

    The demo crash for me !

    • Anonymous

      The demo page is still loading for me. Not sure what you’re seeing. Maybe try cloning the repository and setting it up on your own!

  • Anonymous

    Demo page is showing at the top:
    “No provider could provide the address “3324 N California Ave, Chicago, IL, 60618″No provider could provide the address “11 S White, Frankfort, IL, 60423″No provider could provide the address “1000 Sterling Ave, , Flossmoor, IL, 60422″No provider could provide the address “2053 Ridge Rd, Homewood, IL, 60430″”

    And no map loads.

    • Anonymous

      Are the google API’s blocked in anyway from where you are viewing the demo? The map works fine for me.

      • Anonymous

        Nah, not blocked. Working for me now though. Must have been a temporary hiccup.

  • Dave

    Does this library include any ability for clustering points? (i.e. where multiple points are close together for the current zoom level, they are clustered together into a single point, containing the data for the clustered points)

    • Anonymous

      Leaflet provides an additional clustering library you can use – https://github.com/Leaflet/Leaflet.markercluster

      • Dave

        Thanks, that looks pretty nice. A shame it seems to be a JS client side solution only. I’ll have to take a closer look at it sometime and see if it could be ported to a server side solution. I use a PHP based clusterer at the moment, but it needs running each time a new record is added and is quite slow, so I’m looking for alternatives.