An IP address used to mean nothing to us, other than where they are in the cyber world, but it is now possible to find out where they are in the real world, using databases that arefreely available. In this article, I will disclose where you can get a free database that will tell you the geographic location of every IP address and how to use it.
The database I’m going to use is freely available from MaxMind. They are a company that provides geolocation technologies and credit card fraud detection. We will be using their free database in this article.
Importing the Data
We will be importing the data into a MySQL database, so let’s prep the database first. Create an empty database on your server, and add the following tables:
01.
--
02.
-- Table structure for table `CityBlocks`
03.
--
04.
05.
CREATE
TABLE
`CityBlocks` (
06.
`startIpNum`
bigint
(10)
NOT
NULL
,
07.
`endIpNum`
bigint
(10)
NOT
NULL
,
08.
`locId` mediumint(9)
NOT
NULL
,
09.
KEY
`startIpNum` (`startIpNum`,`endIpNum`)
10.
) ENGINE=MyISAM
DEFAULT
CHARSET=latin1;
01.
--
02.
-- Table structure for table `CityLocation`
03.
--
04.
05.
CREATE
TABLE
`CityLocation` (
06.
`locId` mediumint(9)
NOT
NULL
,
07.
`country`
varchar
(2)
NOT
NULL
,
08.
`region`
varchar
(55)
NOT
NULL
,
09.
`city`
varchar
(55)
NOT
NULL
,
10.
`postalCode`
varchar
(10)
NOT
NULL
,
11.
`latitude`
decimal
(7,4)
NOT
NULL
,
12.
`longitude`
decimal
(7,4)
NOT
NULL
,
13.
`metroCode`
varchar
(3)
NOT
NULL
,
14.
`areaCode`
varchar
(3)
NOT
NULL
,
15.
KEY
`locId` (`locId`)
16.
) ENGINE=MyISAM
DEFAULT
CHARSET=latin1;
There are two tables because one location can have more than one IP address, or even an IP range. For all you database gurus, that would be a one (location) to many (IP addresses) relationship.
Once the database tables are in place, download the latest GeoLite City database in CSV Format. Extract the archive, and make note of where the CSV files are extracted. Next, import the files into their respective tables with these two queries:
01.
--
02.
-- Import data for `CityBlocks`
03.
--
04.
LOAD
DATA
LOCAL
INFILE
'GeoLiteCity-Blocks.csv'
05.
INTO
TABLE
CityBlocks
06.
FIELDS TERMINATED
BY
','
07.
ENCLOSED
BY
'\"'
08.
LINES TERMINATED
BY
'\n'
09.
IGNORE
2 LINES
10.
(startIpNum, endIpNum, locId)
01.
--
02.
-- Import data for `CityLocation`
03.
--
04.
LOAD
DATA
LOCAL
INFILE
'GeoLiteCity-Location.csv'
05.
INTO
TABLE
CityLocation
06.
FIELDS TERMINATED
BY
','
07.
OPTIONALLY ENCLOSED
BY
'\"'
08.
LINES TERMINATED
BY
'\n'
09.
IGNORE
2 LINES
10.
(locId,country,region,city,postalCode,latitude,longitude,metroCode,areaCode)
You may have to insert the absolute path to the data files, so ‘GeoLiteCity-Blocks.csv’ might be ‘/path/to/GeoLiteCity-Blocks.csv’ for a Linux enviornment.
Once these queries are processed, there should be ~3 million records in the CityBlocks table and ~200k records in the CityLocation table. Now we are ready to query this data.
Querying and Using the Data
The first thing we need is the visitor’s IP in long form. PHP has a simple function, ip2long(), that takes care of this. The long version of the IP address is stored in the database because a certain location can contain a range of IP addresses.
PHP will return a signed integer on some systems. I encountered this problem with I developed an application on a 64-bit server, and deployed it on a 32-bit server. When I moved the PHP application, IP addresses wern’t found that should be. Long story short, I learned from this postthat 32-bit systems return a signed integer. We don’t want that, so you will see me use sprintf()with ‘%u’ to force an unsigned integer and eliminate this problem.
Once we get the visitor’s IP address, fetching the data here can be done in two simple queries:
1.
SELECT
locId
FROM
CityBlocks
WHERE
$ip
BETWEEN
startIpNum
AND
endIpNum LIMIT 1
2.
3.
-- PHP code to fetch the row and assign the locID to $locId --
4.
5.
SELECT
*
FROM
CityLocation
WHERE
locId = $locId LIMIT 1
We will get a result like this after the second query:
01.
array
(9) {
02.
[
"locId"
]=>
03.
string(5)
"14237"
04.
[
"country"
]=>
05.
string(2)
"US"
06.
[
"region"
]=>
07.
string(2)
"TX"
08.
[
"city"
]=>
09.
string(6)
"Austin"
10.
[
"postalCode"
]=>
11.
string(5)
"78773"
12.
[
"latitude"
]=>
13.
string(7)
"30.3264"
14.
[
"longitude"
]=>
15.
string(8)
"-97.7713"
16.
[
"metroCode"
]=>
17.
string(3)
"635"
18.
[
"areaCode"
]=>
19.
string(3)
"512"
20.
}
So there you have it. That should be all you need to create your own geolocation app using PHP & MySQL. I will continue with a couple more advanced code samples to show how this can be expanded and applied.
Automating the Data Imports
The script is pretty self-explanatory and commented pretty well, so I won’t explain it line-for line. Replace line ~79 with your own DB connection code, and make sure that the database link is assigned to $db. Please post a comment if you have any questions.
It is not recommended to run this script repeatedly as it puts an unnecessary bandwidth load on maxmind.com when this script is run again and again. If you are using this script, and you have issues where it imports data into the database, comment line ~71 where wit_download_file() is called, so it’s not downloading that huge file every time you try it.
01.
<?php
02.
#
die
(
'remove line 2 before running'
);
/* remove this line before running */
03.
04.
/**
05.
* Download any remote file using PHP's cURL extension
06.
*
07.
* @param string $source the URL to download
08.
* @param string $destination where the file is to be saved
09.
* @return bool
10.
*/
11.
function
wit_download_file(
$source
,
$destination
, &
$err_str
)
12.
{
13.
$destination
=
fopen
(
$destination
,
'w'
);
14.
15.
if
(!
$destination
)
16.
{
17.
return
false;
18.
}
19.
20.
$ch
= curl_init();
21.
22.
curl_setopt(
$ch
, CURLOPT_FILE,
$destination
);
23.
curl_setopt(
$ch
, CURLOPT_HEADER, 0);
24.
curl_setopt(
$ch
, CURLOPT_URL,
$source
);
25.
26.
curl_exec(
$ch
);
27.
$err_str
= curl_error(
$ch
);
28.
curl_close(
$ch
);
29.
30.
fclose(
$destination
);
31.
32.
return
(
$err_str
==
''
) ? true : false;
33.
}
34.
35.
/**
36.
* actual script starts here
37.
*/
38.
header(
'Content-type: text/plain'
);
39.
40.
$link
=
'http://geolite.maxmind.com/download/geoip/database/GeoLiteCity_CSV/GeoLiteCity-latest.zip'
;
41.
42.
/* download the file - may take some time */
43.
echo
"Downloading data file...\n"
;
44.
wit_download_file(
$link
,
'GeoLiteCity.zip'
,
$errstr
)
or
die
(
'Failed to download data file: '
.
$errstr
);
45.
46.
/* extract the data file */
47.
echo
"Extracting...\n"
;
48.
if
(!
is_dir
(
'geoip'
))
mkdir
(
'geoip'
, 0744);
49.
shell_exec(
'unzip -oj GeoLiteCity.zip -d geoip/'
);
50.
51.
echo
"Inserting into database...\n"
;
52.
require
(
'../includes/database.php'
);
53.
$db
= db_connect();
54.
55.
/* load the CityBlocks data */
56.
mysql_query(
"TRUNCATE TABLE CityBlocks"
);
57.
$query
= "LOAD DATA LOCAL INFILE
'geoip/GeoLiteCity-Blocks.csv'
58.
INTO TABLE CityBlocks FIELDS TERMINATED BY
','
ENCLOSED BY
'\"'
59.
LINES TERMINATED BY
'\n'
IGNORE 2 LINES (startIpNum, endIpNum, locId)";
60.
$result
= mysql_query(
$query
);
61.
62.
/* load the location data */
63.
mysql_query(
"TRUNCATE TABLE CityLocation"
);
64.
$query
= "LOAD DATA LOCAL INFILE
'geoip/GeoLiteCity-Location.csv'
65.
INTO TABLE CityLocation FIELDS TERMINATED BY
','
OPTIONALLY ENCLOSED BY
'\"'
66.
LINES TERMINATED BY
'\n'
IGNORE 2 LINES
67.
(locId,country,region,city,postalCode,latitude,longitude,metroCode,areaCode)";
68.
$result
= mysql_query(
$query
);
69.
70.
echo
"Optimizing Tables...\n"
;
71.
mysql_query(
"OPTIMIZE TABLE CityBlocks"
);
72.
mysql_query(
"OPTIMIZE TABLE CityLocation"
);
73.
74.
echo
"Deleting temp directory...\n"
;
75.
shell_exec(
'rm -rf geoip/'
);
76.
77.
echo
"Done!\n"
;
78.
?>
Geolocation Sample
Here is a simple example that gets the location of the current visitor, according to Maxmind’s free GeoCity database, and plots it on a Google Map. Please note that you will have to change the Google API Key if you plan on using this code on your own site.
01.
<?php
02.
/* replace with your own DB connection code */
03.
require
(
'../includes/database.php'
);
04.
$db
= db_connect();
05.
06.
/* get the IP address and make sure it is an unsigned integer */
07.
$ip
= sprintf(
'%u'
,
ip2long
(
$_SERVER
[
'REMOTE_ADDR'
]));
08.
09.
/* fetch the location id */
10.
$query
=
"SELECT locId FROM CityBlocks WHERE $ip BETWEEN startIpNum AND endIpNum LIMIT 1"
;
11.
$result
= mysql_query(
$query
,
$db
)
or
die
(mysql_error());
12.
$row
= mysql_fetch_assoc(
$result
);
13.
14.
/* now fetch the location */
15.
$locId
=
$row
[
'locId'
];
16.
$query
=
"SELECT * FROM CityLocation WHERE locId = $locId LIMIT 1"
;
17.
$result
= mysql_query(
$query
,
$db
)
or
die
(mysql_error());
18.
$location
= mysql_fetch_assoc(
$result
);
19.
?>
20.
<!DOCTYPE HTML PUBLIC
"-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd"
>
21.
<html>
22.
<head>
23.
<title>IP Geolocation Example</title>
24.
<meta name=
"generator"
content=
"Bluefish 1.0.7"
>
25.
<meta name=
"author"
content=
"Andrew Wells"
>
26.
<meta name=
"date"
content=
"2008-12-26T16:48:50-0600"
>
27.
<meta http-equiv=
"content-type"
content=
"text/html; charset=UTF-8"
>
28.
<meta http-equiv=
"content-type"
content=
"application/xhtml+xml; charset=UTF-8"
>
29.
<script src=
"http://maps.google.com/maps?file=api&;amp;v=2&key=ABQIAAAAx6XuOYsDLrUX-imS2wkJshQcGiGm0nt-HEPscddBWrEOoLM0sRSH68H2XyBqlLiKczSojl9wJMHkqw"
type=
"text/javascript"
></script>
30.
<script type=
"text/javascript"
>
31.
//<![CDATA[
32.
function
load()
33.
{
34.
if
(GBrowserIsCompatible())
35.
{
36.
var
map =
new
GMap2(document.getElementById(
"map"
));
37.
map.setCenter(
new
GLatLng(<?=
$location
[
'latitude'
]?>, <?=
$location
[
'longitude'
]?>), 10);
38.
map.addControl(
new
GMapTypeControl());
39.
map.addControl(
new
GSmallMapControl());
40.
41.
var
point =
new
GLatLng(<?=
$location
[
'latitude'
]?>, <?=
$location
[
'longitude'
]?>);
42.
marker =
new
GMarker(point);
43.
map.addOverlay(marker);
44.
marker.openInfoWindowHtml(
'Lat/Lon: <?=$location['
latitude
']?>, <?=$location['
longitude
']?><br />City: <?=$location['
city
']?><br />Region: <?=$location['
region
']?><br />'
);
45.
}
46.
}
47.
//]]>
48.
</script>
49.
50.
</head>
51.
<body onload=
"load()"
onunload=
"GUnload()"
>
52.
<h2>Map
for
(<?=
$_SERVER
[
'REMOTE_ADDR'
]?>):</h2>
53.
<div id=
"map"
style=
"width:800px;height:600px"
></div>
54.
55.
<h2>Details from DB Record:</h2>
56.
<pre><?php var_dump(
$location
); ?></pre>
57.
</body>
58.
</html>
The application possibilities with this data is limitless. Companies have been known to use this data for targeted advertising, usage statistics, and even fraud detection. How you will use it in your own application is up to you.