PHP Geocoding Tutorial

This is a tutorial for using the OpenCage geocoding API in PHP.

Topics covered in this tutorial

Background

The code examples below will use your geocoding API key once you log in.

Before we dive in to the tutorial

  1. Sign up for an OpenCage geocoding API key.
  2. Play with the demo page, so that you see the actual response the API returns.
  3. Browse the API reference, so you understand the optional parameters, best practices, possible response codes, and the rate limiting on free trial accounts.
Working with AI?

We offer an Agent Skill to make it easy for AI to quickly learn about our geocoding API.

Agent Skill / Working with AI

Are you developing with AI?

We offer an Agent Skill that includes a reference file specifically about developing code to access our API via PHP.

PHP libraries for accessing the OpenCage Geocoding API

There are two PHP libraries you can use:

The library uses the Guzzle HTTP client to access the API. Both synchronous and asynchronous (parallel) requests are supported.

PHP 8.2 or newer is required. For PHP 8.0 or 8.1 use the 3.x series of the library.

The recommended - and easiest way - to install is via Composer. Require the library in your project's composer.json file.

composer require opencage/geocode

Import the Geocoder class.

require "vendor/autoload.php";

Geocode an address (forward geocoding)

$geocoder = new \OpenCage\Geocoder\Geocoder('YOUR-API-KEY');
# no need to URI encode the query, the library does this for you
$result = $geocoder->geocode('82 Clerkenwell Road, London, UK');
print_r($result);

# set optional parameters
# see the full list: https://opencagedata.com/api#optional-params
#
$result = $geocoder->geocode('6 Rue Massillon, 30020 Nîmes, France', ['language' => 'fr', 'countrycode' => 'fr']);
if ($result && $result['total_results'] > 0) {
  $first = $result['results'][0];
  print $first['geometry']['lng'] . ';' . $first['geometry']['lat'] . ';' . $first['formatted'] . "\n";
  # 4.360081;43.8316276;6 Rue Massillon, 30020 Nîmes, Frankreich
}

Geocode coordinates (reverse geocoding)

$geocoder = new \OpenCage\Geocoder\Geocoder('YOUR-API-KEY');
$result = $geocoder->geocodeReverse(43.831, 4.360); # latitude, longitude
print $result['results'][0]['formatted'];
# 3 Rue de Rivarol, 30020 Nîmes, France

Batch geocode addresses

$geocoder = new \OpenCage\Geocoder\Geocoder('YOUR-API-KEY');

$addresses = ['London', 'Paris, France', 'Berlin'];
$results = [];

foreach ($addresses as $address) {
  $result = $geocoder->geocode($address);
  $msg = $result['status']['message'];
  if ($msg == 'OK'){
      $results[$address] = $result;
  } else {
      error_log("failed to geocode '$address' : $msg");
  }
}

Set optional parameters

See the full list

$result = $geocoder->geocode('6 Rue Massillon, 30020 Nîmes, France', [
    'language' => 'fr',
    'countrycode' => 'fr'
]);
if ($result && $result['total_results'] > 0) {
  $first = $result['results'][0];
  print $first['geometry']['lng'] . ';' . $first['geometry']['lat'] . ';' . $first['formatted'] . "\n";
  // 4.360081;43.8316276;6 Rue Massillon, 30020 Nîmes, Frankreich
}

Configuration

Set a request timeout (in seconds, the default is 10).

$geocoder->setTimeout(5);

Set a proxy URL. The URL must include a scheme (http, https, or socks5) and a host, and must be set before calling geocode().

$geocoder->setProxy('https://proxy.example.com:1234');
$result = $geocoder->geocode("Brandenburger Tor, Berlin");
print_r($result['results'][0]['formatted']);
// Brandenburger Tor, Unter den Linden, 10117 Berlin, Germany
print_r($result['results'][0]['geometry']);
// Array
// (
//    [lat] => 52.5166047
//    [lng] => 13.3809897
// )

Geocoding faster by running queries in parallel

The library can run multiple requests concurrently using its asynchronous methods, which is much faster than geocoding a list one at a time.

$geocoder = new \OpenCage\Geocoder\Geocoder('YOUR-API-KEY');

# a single async request returns a promise
$promise = $geocoder->geocodeReverseAsync(51.5074, -0.1278);
$result = $promise->wait();

# run several reverse geocoding requests concurrently
$promises = [
    'london' => $geocoder->geocodeReverseAsync(51.5074, -0.1278),
    'paris'  => $geocoder->geocodeReverseAsync(48.8566, 2.3522),
    'tokyo'  => $geocoder->geocodeReverseAsync(35.6762, 139.6503),
];
$results = \GuzzleHttp\Promise\Utils::unwrap($promises);
print $results['london']['results'][0]['formatted'];

Free trial keys are limited to one request per second. To stay within that limit, send the requests in chunks and pause between each chunk. Raise $chunkSize if your plan allows a higher rate.

$geocoder = new \OpenCage\Geocoder\Geocoder('YOUR-API-KEY');

$addresses = [
    '82 Clerkenwell Road, London, EC1M 5RF, UK',
    '6 Rue Massillon, 30020 Nîmes, France',
    'Brandenburger Tor, 10117 Berlin, Germany',
    'Plaza Mayor, 28012 Madrid, Spain',
];

$chunkSize = 1;
$results = [];

foreach (array_chunk($addresses, $chunkSize) as $chunk) {
    $promises = [];
    foreach ($chunk as $address) {
        $promises[$address] = $geocoder->geocodeAsync($address);
    }
    # wait for this chunk to finish before starting the next one
    $results += \GuzzleHttp\Promise\Utils::unwrap($promises);

    sleep(1); # pause before the next chunk
}

foreach ($results as $address => $result) {
    print $address . ' => ' . $result['results'][0]['formatted'] . "\n";
}

We also created an example how to geocode a file with parallel threads.

Before you start geocoding at high volume, please read our guide to geocoding large datasets where we explain various strategies and points to consider.

The recommended - and easiest way - to install is via Composer. Require the library in your project's composer.json file.

composer require willdurand/geocoder

Import the Geocoder class.

require "vendor/autoload.php";

use Http\Adapter\Guzzle6\Client as GuzzleAdapter;
use Geocoder\Query\GeocodeQuery;
use Geocoder\Query\ReverseQuery;

Geocode an address (forward geocoding)

$adapter  = new GuzzleAdapter();
$provider = new \Geocoder\Provider\OpenCage\OpenCage($adapter, 'YOUR-API-KEY');
$geocoder = new \Geocoder\StatefulGeocoder($provider, 'en');

$results = $geocoder->geocodeQuery(GeocodeQuery::create('1 Hacker Way, Menlo Park, 94025'));
# print_r($results);

$coords = $results->first()->getCoordinates();

echo json_encode([ 'lat' => $coords->getLatitude(), 'lon' => $coords->getLongitude() ]) . "\n";

Batch geocode addresses

$adapter  = new GuzzleAdapter();
$provider = new \Geocoder\Provider\OpenCage\OpenCage($adapter, 'YOUR-API-KEY');
$geocoder = new \Geocoder\StatefulGeocoder($provider, 'en');

$addresses = ['London', 'Paris', 'Berlin'];
$results = [];

foreach ($addresses as $address) {
  $result = $geocoder->geocodeQuery(GeocodeQuery::create($address));
  $msg = $result['status']['message'];
  if ($msg === 'OK') {
      $results[$address] = $result;
  } else {
      error_log("failed to geocode '$addresses' : $msg");
  }
}

Geocode coordinates (reverse geocoding)

$adapter  = new GuzzleAdapter();
$provider = new \Geocoder\Provider\OpenCage\OpenCage($adapter, 'YOUR-API-KEY');
$geocoder = new \Geocoder\StatefulGeocoder($provider, 'en');

$results = $geocoder->reverseQuery(ReverseQuery::fromCoordinates(37.4856225, -122.1468803));
# print_r($results);

echo $results->first()->getStreetName() . "\n";

Start your free trial

2,500 geocoding API requests/day - No credit card required