在 Web 開發中,根據訪客的 IP 地址獲取其地理位置(國家、城市、經緯度)是常見需求,例如提供本地化內容、統計訪客分佈或實現「附近的人」功能。PHP 的 GeoIP 擴展結合 MaxMind 提供的免費資料庫,能高效完成此任務。本文將詳細介紹在 Linux 環境下安裝 GeoIP 擴展、配置資料庫、查詢 IP 地理信息,以及利用經緯度計算兩點距離並實現範圍查詢的完整流程。
首先需要安裝 GeoIP 開發庫:
sudo apt-get install libgeoip-dev
安裝指定版本的 geoip 擴展(Beta 版需指定版本號):
pecl install geoip-1.1.0
提示:若使用 apt 安裝的 PHP,可直接安裝 php5-geoip 包。
在 php.ini 中添加擴展載入與資料庫目錄配置:
extension=geoip.so geoip.custom_directory="/usr/share/GeoIP"
MaxMind 提供免費的 GeoLiteCity 資料庫(解壓後約 18MB),下載地址:
https://dev.maxmind.com/geoip/legacy/install/city/
下載、解壓並移動到指定目錄:
wget https://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz gunzip GeoLiteCity.dat.gz sudo mkdir -v /usr/share/GeoIP sudo mv -v GeoLiteCity.dat /usr/share/GeoIP/GeoIPCity.dat
php -a
<?php
print_r(geoip_record_by_name('106.37.165.80'));
// 按 Ctrl+D 運行
/*
輸出範例:
Array
(
[continent_code] => AS
[country_code] => CN
[country_code3] => CHN
[country_name] => China
[region] => 22
[city] => Beijing
[postal_code] =>
[latitude] => 39.928901672363
[longitude] => 116.38829803467
[dma_code] => 0
[area_code] => 0
)
*/
先安裝工具包:
sudo apt-get install geoip-bin geoip-database
查看 IP 信息(以 61.145.122.155 為例):
geoiplookup 61.145.122.155 -f /usr/share/GeoIP/GeoIP.dat # GeoIP Country Edition: CN, China geoiplookup 61.145.122.155 -f /usr/share/GeoIP/GeoIPCity.dat # GeoIP City Edition, Rev 1: CN, 30, Guangdong, Guangzhou, N/A, 23.116699, 113.250000, 0, 0
說明: geoip-database 提供的 GeoIP.dat 僅精確到國家;從 MaxMind 官網下載的 GeoLiteCity 則包含城市、經緯度等詳細信息。直接執行 geoiplookup IP 會同時顯示兩個資料庫的結果。
$record = geoip_record_by_name($_SERVER['REMOTE_ADDR']); $lat = $record['latitude']; // 緯度 $lon = $record['longitude']; // 經度
注意: 返回值中,西經和南緯為負數。
$y = 5000 / 110852; // 緯度範圍
$x = 5000 / (111320 * cos($lat)); // 經度範圍
$sql = "SELECT * FROM `user`
WHERE `lat` BETWEEN {$lat} - {$y} AND {$lat} + {$y}
AND `lon` BETWEEN {$lon} - {$x} AND {$lon} + {$x};";
資料表需預先存儲用戶的 lat(緯度)與 lon(經度)字段。上述 SQL 查出的是一個矩形粗略範圍,需進一步用精確公式過濾超出 5 公里的用戶。
function distance($lat1, $lon1, $lat2, $lon2) {
$R = 6371393; // 地球平均半徑(米)
$dlat = deg2rad($lat2 - $lat1);
$dlon = deg2rad($lon2 - $lon1);
$a = pow(sin($dlat / 2), 2)
+ cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * pow(sin($dlon / 2), 2);
$c = 2 * atan2(sqrt($a), sqrt(1 - $a));
$d = $R * $c;
return round($d);
}
// 測試:赤道上經度相差 1 度的距離
echo distance(0, 0, 0, 1); // 約 111202 米
$users = [
'win' => ['dis' => 1024, 'age' => 31],
'osx' => ['dis' => 512, 'age' => 15],
'lin' => ['dis' => 512, 'age' => 25],
];
foreach ($users as $k => $v) {
$sort['dis'][$k] = $v['dis'];
$sort['age'][$k] = $v['age'];
}
// 先按距離升序,距離相同則按年齡降序
array_multisort($sort['dis'], SORT_ASC, $sort['age'], SORT_DESC, $users);
echo json_encode($users);
// {"lin":{"dis":512,"age":25},"osx":{"dis":512,"age":15},"win":{"dis":1024,"age":31}}
本文完整演示了:
libgeoip-dev 與 pecl install geoipGeoLiteCity.dat 至 /usr/share/GeoIP/GeoIPCity.datgeoip_record_by_name() 獲取國家、城市、經緯度這套方案適用於「附近的人」、門店定位、區域統計等場景,配合資料庫空間索引(如 MySQL SPATIAL INDEX 或 PostgreSQL PostGIS)可進一步提升大數據量下的查詢性能。