Building an IP Geolocator with MaxMind GeoLite City

This article explains how to add an IP geolocator to your website using the MaxMind GeoLite City database.

1. Introduction

IP Geolocation is the process of finding the geographical location of a website visitor. Finding the location of your website visitors enables you to serve location-specific content. This article explains how to add an IP geolocator to your web pages with the MaxMind GeoLite City database. MaxMind GeoLite City provides the following information for an IP address:

  • two-letter country code;
  • three-letter country code;
  • country name;
  • region name;
  • city name;
  • city longitude; and
  • city latitude.

I’ll add the following country information:

  • two-letter continent code;
  • continent name;
  • country longitude; and
  • country latitude.

I’ll also add the following time information:

  • time zone of the country; and
  • local time in the country.

Before explaining how to add the extra continent and time zone information, I’ll introduce a more consistent and easy-to-use interface to the GeoLite City database.

2. Encapsulating MaxMind

Given a reference to the GeoLite City database and an IP address, MaxMind’s geoip_record_by_addr function returns a PHP object that contains country and city information for the IP address:

$gi = geoip_open("GeoLiteCity.dat", GEOIP_STANDARD);
$record = geoip_record_by_addr($gi, $ip);
geoip_close($gi);

The following table lists the references to the $record object required to access each piece of information:

Two-Letter Country Code $record->country_code;
Three-Letter Country Code $record->country_code3;
Country Name $record->country_name;
Region Name $GEOIP_REGION_NAME[$record->country_code][$record->region];
City Name $record->city;
City Latitude $record->latitude;
City Longitude $record->longitude;

To get the name of the region we need to look it up in $GEOIP_REGION_NAME, the MaxMind array that maps the two-letter code of the country and a MaxMind region code onto the name of the region. Even at this early stage, accessing information from MaxMind’s object structure is becoming untidy. Furthermore, the GeoLite City database doesn’t provide continent or time zone information.

The geolocation.php file contains the ip2c_geolocation function that retrieves MaxMind’s information and adds the country and time zone information. This function returns an array that provides a consistent interface to the GeoLite City information and the extra continent and time zone information. For example, the following PHP statement retrieves the geolocation information for the IP address 66.102.9.104 and stores it in the $geolocation array:

$geolocation = ip2c_geolocation("66.102.9.104");

To retrieve the geolocation information for your website visitors, use PHP’s getenv("REMOTE_ADDR") function:

$ip = getenv("REMOTE_ADDR");
$geolocation = ip2c_geolocation($ip);

The following table lists the array elements that hold each piece of geolocation information:

Two-Letter Continent Code $geolocation["continentCode"]
Continent Name $geolocation["continentName"]
Two-Letter Country Code $geolocation["countryCode2"]
Three-Letter Country Code $geolocation["countryCode3"]
Country Name $geolocation["countryName"]
Region Name $geolocation["regionName"]
City Name $geolocation["cityName"]
City Latitude $geolocation["cityLatitude"]
City Longitude $geolocation["cityLongitude"]
Country Latitude $geolocation["countryLatitude"]
Country Longitude $geolocation["countryLongitude"]

3. Adding the Continent

The continents.php file contains two arrays, $IP2C_CONTINENT and $IP2C_CONTINENT_NAME, that provide the continent information for a country. The ip2c_geolocation function looks up the name of a continent and its two-letter code in these two arrays.

(All of the arrays used in this article start with IP2C_ to follow the naming convention of the functions used in this article, which all start with ip2c_, a shortened form of the phrase IP to country.)

The $IP2C_CONTINENT array maps a two-letter country code onto a two-letter continent code:

$IP2C_CONTINENT = array(
   "AF" => "AS",
   "AX" => "EU",
   "AL" => "EU",
   "DZ" => "AF",
   "AS" => "OC",
   ...
   "WF" => "OC",
   "EH" => "AF",
   "YE" => "AS",
   "ZM" => "AF",
   "ZW" => "AF"
)

The $IP2C_CONTINENT_NAME array maps a two-letter continent code onto the name of the continent:

$IP2C_CONTINENT_NAME = array(
   "AS" => "Asia",
   "AN" => "Antarctica",
   "AF" => "Africa",
   "SA" => "South America",
   "EU" => "Europe",
   "OC" => "Oceania",
   "NA" => "North America"
);

For example, the following PHP statements return the name of the continent that contains the United Kingdom, which is Europe:

$continentCode = $IP2C_CONTINENT['GB'];                // returns EU
$continentName = $IP2C_CONTINENT_NAME[$continentCode]; // returns Europe

4. Adding Country Longitude and Latitude

The countries.php file contains the $IP2C_COUNTRY_LOCATION array that maps a two-letter country code onto a two-element array containing the longitude and latitude of the centre of the country:

$IP2C_COUNTRY_LOCATION = array(
   "AD" => array(42.50, 1.50),
   "AE" => array(24.00, 54.00),
   "AF" => array(33.00, 65.00),
   "AG" => array(17.05, -61.80),
   "AI" => array(18.22, -63.05),
   ...
   "YU" => array(44.00, 21.00),
   "ZA" => array(-30.00, 26.00),
   "ZM" => array(-15.00, 30.00),
   "ZR" => array(-1.00, 22.00),
   "ZW" => array(-19.00, 29.00)
);

For example, the following PHP statements return the latitude and longitude of the centre of the USA, which has two-letter country code, US:

$latitude  = $IP2C_COUNTRY_LOCATION['US'][0]; // returns 38.00
$longitude = $IP2C_COUNTRY_LOCATION['US'][1]; // returns -98.00

5. Telling the Local Time

The timeZones.php file contains the $IP2C_TIME_ZONES array that maps a two-letter country code onto a string or an array:

$IP2C_TIME_ZONES = array(
   "AD" => "Europe/Andorra",
   "AE" => "Asia/Dubai",
   "AF" => "Asia/Kabul",
   "AG" => "America/Antigua",
   "AI" => "America/Anguilla",
   "AL" => "Europe/Tirane",
   "AM" => "Asia/Yerevan",
   "AN" => "America/Curacao",
   "AO" => "Africa/Luanda",
   "AQ" => array(
      array("Antarctica/Casey", 110.516667),
      array("Antarctica/Davis", 77.966667),
      array("Antarctica/DumontDUrville", 140.016667),
      array("Antarctica/Mawson", 62.883333),
      array("Antarctica/McMurdo", 166.6),
      array("Antarctica/Palmer", -64.1),
      array("Antarctica/Rothera", -68.133333),
      array("Antarctica/South_Pole", 0),
      array("Antarctica/Syowa", 39.59),
      array("Antarctica/Vostok", 106.9)
   ),
   ...
   "YE" => "Asia/Aden",
   "YT" => "Indian/Mayotte",
   "ZA" => "Africa/Johannesburg",
   "ZM" => "Africa/Lusaka",
   "ZW" => "Africa/Harare"
);

A two-letter country code maps onto a string when the country has a single time zone. The value of the string is the name of the time zone. For example, the two-letter country code of the United Kingdom, GB, maps onto a string containing the name of the UKs single time zone:

"GB" => "Europe/London"

When a country has more than one time zone, its two-letter country code maps onto an array, each element of which is a two-element array that contains the name of one of the country’s time zones and the longitude of the centre of the time zone. For example, the two-letter country code of Australia, AU, maps onto an array that represents Australia’s twelve time zones:

"AU" => array(
   array("Australia/Adelaide",    138.583333),
   array("Australia/Brisbane",    153.033333),
   array("Australia/Broken_Hill", 141.45),
   array("Australia/Currie",      143.866667),
   array("Australia/Darwin",      130.833333),
   array("Australia/Eucla",       128.866667),
   array("Australia/Hobart",      147.316667),
   array("Australia/Lindeman",    149),
   array("Australia/Lord_Howe",   159.083333),
   array("Australia/Melbourne",   144.966667),
   array("Australia/Perth",       115.85),
   array("Australia/Sydney",      151.216667)
)

The localTime.php file contains the following functions that together return the local time in a country:

  • ip2c_getTimeZone
  • ip2c_getLocalTime
  • _ip2c_getNearestTimeZone

Given a two-letter country code and a longitude, the ip2c_getTimeZone function returns the time zone for the specified country. If the country has more than one time zone, this function returns the time zone nearest to the specified longitude. If ip2c_getTimeZone can’t identify the time zone, it returns “Unknown”.

function ip2c_getTimeZone($countryCode2, $longitude) {
   $timeZone = "Unknown";
   global $IP2C_TIME_ZONES;
   if (array_key_exists($countryCode2, $IP2C_TIME_ZONES)) {
      $timeZoneInfo = $IP2C_TIME_ZONES[$countryCode2];
      if (is_array($timeZoneInfo)) {
         $timeZone = _ip2c_getNearestTimeZone($timeZoneInfo, $longitude);
      }
      else {
         $timeZone = $timeZoneInfo;
      }
   }
   return $timeZone;
}

Given an array containing the time zones of a country and a longitude, the _ip2c_getNearestTimeZone function returns the time zone nearest to the specified longitude. This function is prefixed with an underscore to indicate that it is a utility function used by the ip2c_getTimeZone function; it is not designed to be called directly.

After retrieving the name of the time zone with the ip2c_getTimeZone function, use the ip2c_getLocalTime function to return the local time. Given the name of a time zone, the ip2c_getLocalTime function returns the local time in unix epoch seconds, or zero if the name of the time zone is “Unknown”.

function ip2c_getLocalTime($timeZone) {
   $epoch = 0;
   if ($timeZone != "Unknown") {
      $command = "localTime.pl $timeZone";
      $output = shëll_exec($command);
      if ($output != "" and is_int($output * 1)) {
         $epoch = $output;
      }
   }
   return $epoch;
}

The ip2c_getLocalTime function calls the localTime.pl Perl script to return the local time in unix epoch seconds:

#!/usr/bin/perl

# localTime.pl

# Return the current time in epoch seconds in the specified time zone

use DateTime;
use DateTime::TimeZone;

$timeZoneName = $ARGV[$argnum];
$timeZone     = DateTime::TimeZone->new(name => $timeZoneName);
$timeNow      = time();
$dateTime     = DateTime->from_epoch(epoch => $timeNow);
$offset       = $timeZone->offset_for_datetime($dateTime);
$localTime    = $timeNow + $offset;
print "$localTime";

For example, to return the local time in the capital of Malaysia, Kuala Lumpur, type the following at the command line:

localTime.pl Asia/Kuala_Lumpur

6. Geolocating with PHP

The geolocatorTemplate.php file contains a template for creating your own IP geolocator. This template assumes that the following files are located in the same directory as the template:

Additionally, the template assumes that the following MaxMind files are in the same directory as the template:

You need to download these files from MaxMind.

The template produces a web page that displays the following information for the visitor:

  • IP address;
  • continent name;
  • two-letter continent code;
  • country name;
  • two-letter country code;
  • three-letter country code;
  • country longitude;
  • country latitude;
  • region name;
  • city name;
  • city longitude;
  • city latitude;
  • time zone of the country; and
  • local time in the country in unix epoch seconds.