转自:http://www1.huachu.com.cn/read/readbookinfo.asp?sectionid=1000004203
|
||||
在初步了解Google Maps API后,接下来就可以开始学习本章核心内容——根据IP定位地理位置。本节将初步介绍根据IP定位地理位置这项技术的背景,通过实例讲解如何利用MaxMind®提供的开源数据库查询某IP所在地理位置,并最终完成IP定位查询和Google地图的整合。 3.2.1 IP定位地理位置概述及准备工作IP定位地理位置并不是一项很新的技术,从显示QQ用户的位置到站点统计,到处都有其应用。不过相对其发展潜力而言,目前的应用还是很不够的。例如广告方面,有地域针对性地投放是很有必要的,把北京某超市促销的广告显示给来自上海的用户看的收效是可想而知的。当然,这不是本书重点,不在深究,下面将介绍其基本原理及常用的IP地理位置数据库。 IP和地理位置的关系实际上比较简单,因为大量IP段是根据地域来分配的,这就为查询IP对应的地理位置带来了方便。此外,还有相当一部分IP是固定分配的,长期以来都不会改变,这就是著名的“纯真IP数据库甚至能定位到某些网吧”。必须承认,这种数据库不可能100%准确,因为存在大量IP是动态分配的,而且即使是固定IP也会出现变更而数据库来不及更新的情况。当然,即使无法100%地精确定位,IP定位地理位置技术依旧是很有意义的。 目前网络上中国人比较熟悉的就是纯真IP数据库了。不过和其他国产免费IP数据库一样,该IP库无法用于IP地理位置可视化查询系统。原因是该数据库只提供文本的地理位置信息,而并未提供相应的经纬度数据。比较流行的国外数据库有MaxMind®的GeoIP®数据库和IP2Location™的相应数据库。 因为MaxMind®除了发布商业授权的数据库外,还发布了GPL授权的免费GeoLiteCity数据库,并提供丰富的API支持,所以本章将以该数据库为基础进行详细介绍。 GPL发布的GeoLiteCity数据库下载地址为http://www. maxmind.com/download /geoip/database/。该数据库分为两个版本,一个为方便导入MySQL、Microsoft SQL Server等数据库的CSV数据库,另一个为二进制版的数据库。这里我们下载后者http://www. maxmind.com/download/geoip/database/GeoLiteCity.dat.gz,因为该版本官方提供查询API,且经过官方优化,效率更高。下载完成后用WinRAR等压缩工具解压为GeoLiteCity.dat即可。 除此之外需要的下载的有官方提供的API,见http://www.maxmind.com/app/php。本例中将使用免费的PHP版API,下载地址为http://www.maxmind.com/download/ geoip/api/php/,需要的文件为geoip.inc、geoipcity.inc和geoipregionvars.php。 将以上所有文件(包括GeoLiteCity数据库)放到网页服务器的同一目录(例如D:wwwgeoip),准备工作就完成了。 3.2.2 利用GeoIP®数据库及API进行地理定位查询地理定位查询主要可分为两个步骤:第一,获取待查询的IP;第二,利用MaxMind® API进行查询并返回查询结果。本例中数据比较简单,因此可以把查询结果直接以Javascript字符串的形式返回给客户端,用eval调用即可。下面将详细介绍上述步骤。 1.获取待查询的IP首先,在文件开始的部分引入MaxMind® API的库文件。 <?php //导入库文件 include("geoipcity.inc"); include("geoipregionvars.php") ?> 如果未传递任何参数,则使用当前访客的IP,如此就可以在加载的时候使用显示当前访客的信息了。代码如下。 //接上面程序 //获取客户端IP的函数 function getClientIP() { if (isset($HTTP_SERVER_VARS["HTTP_X_FORWARDED_FOR"])) { $ip = $HTTP_SERVER_VARS["HTTP_X_FORWARDED_FOR"]; } elseif (isset($HTTP_SERVER_VARS["HTTP_CLIENT_IP"])) { $ip = $HTTP_SERVER_VARS["HTTP_CLIENT_IP"]; } elseif (isset($HTTP_SERVER_VARS["REMOTE_ADDR"])) { $ip = $HTTP_SERVER_VARS["REMOTE_ADDR"]; } elseif (getenv("HTTP_X_FORWARDED_FOR")) { $ip = getenv("HTTP_X_FORWARDED_FOR"); } elseif (getenv("HTTP_CLIENT_IP")) { $ip = getenv("HTTP_CLIENT_IP"); } elseif (getenv("REMOTE_ADDR")) { $ip = getenv("REMOTE_ADDR"); } else { $ip = false; } return $ip; }
//如果传递的查询参数为空 if(empty($_GET['q'])) { $ip = getClientIP(); if (!isset($ip)) { echo 'alert("Cannot get your IP address!");'; die(); } } 如果有参数传递,则判断是否为IP或域名信息。如果都不是,报错,终止查询。代码如下。 //接上面程序 //如果传递的查询参数不为空 else { //探测字符串是否为IP的正则表达式 $pattern = "/^(d{1,2}|1dd|2[0-4]d|25[0-5]).(d{1,2}|1dd|2[0-4]d|25[0-5]).(d{1,2}|1dd|2[0-4]d|25[0-5]).(d{1,2}|1dd|2[0-4]d|25[0-5])$/";
if(preg_match($pattern, $_GET['q'])) //如果查询的信息为IP { $ip = $_GET['q']; } else //如果查询的信息为域名 { $ip = gethostbyname($_GET['q']); //若域名查询成功,则返回的字符串为IP if(!preg_match($pattern, $ip)) //检测是否返回IP,若未返回IP,即域名查询失败,报错 { echo 'alert("Invalid Input!"); '; die(); } } } 经过以上一系列处理,就可以从参数中获取真实的IP用于查询了。 2.通过IP获取地理信息利用MaxMind®取得某IP的地理信息是相对容易的,查询的结果是一个结构体,包含国家代码、国家名称、城市名称和城市经纬度等信息。下面对其中元素进行一一说明。 — country_code – 国家代码(两位),如中国为CN。 — country_code3 – 国家代码(三位),如中国为CHN。 — country_name – 国家名称(英文),如中国为China。 — region – 区域代码,通过$GEOIP_REGION_NAME[$record->country_code] [$record->region]可获得区域的名称。对于中国,region得到的是省级行政区的名称,如$GEOIP_REGION_NAME["CN"]["01"]对应的行政区即位Anhui(安徽)。 — city – 城市名称。 — postal_code – 邮编,仅美国可用。 — latitude – 纬度。 — longitude – 经度。 — dma_code – DMA代码,仅美国可用。 — area_code – 地区代码,仅美国可用。 了解数据的组织结构后就可以方便地使用其中的数据了。代码如下: //接上面程序
//如果取消下一行将使用共享内存打开GeoLiteCity数据库,可加快查询。但前提是服务器支持共享内存 //$gi = geoip_open("./GeoLiteCity.dat",GEOIP_MEMORY_CACHE);
//以常规方式打开GeoLiteCity数据库,一般情况下都可以使用 $gi = geoip_open("./GeoLiteCity.dat",GEOIP_STANDARD);
//以上是假定GeoLiteCity数据库和PHP文件在同一个目录,故使用"./GeoLiteCity.dat"为路径 //如果不是,请改为实际目录
//获取IP的信息 $record = geoip_record_by_addr($gi, $ip); //关闭数据库 geoip_close($gi);
//如果获取了相关数据 if($record) { echo 'loadGeoInfo('. '"'. $_GET['q'] .'",'. //原始查询信息 '"'. $ip .'",'. //被查IP '"'. $record->country_code .'",'. //国家代码(两位) '"'. $record->country_code3 .'",'. //国家代码(三位) '"'. $record->country_name .'",'. //国家名称 '"'. $GEOIP_REGION_NAME[$record->country_code] [$record->region] .'",'. //地区名称 '"'. $record->city .'",'. //城市名称 $record->latitude .','. //纬度 $record->longitude .')'; //经度 //如果数据库中不存在相关数据 else { echo 'alert("The Information for '.$ip.' is not available now!"); '; } ?> 将以上代码整合到一起,保存为search.php,放到本程序的主目录中(search.php完整代码在光盘中本章节目录里可以找到)。 在浏览器中输入http://服务器地址/程序所在路径/search.php?q=google.com,应该会显示与下面信息类似的输出结果。 loadGeoInfo("google.com", "64.233.187.99", "US", "USA", "United States", "California", "Mountain View", 37.4192,-122.0574) 而输入http://服务器地址/程序所在路径/search.php?q=202.114.64.139,则输出应该和下面类似。 loadGeoInfo("202.114.64.139", "202.114.64.139", "CN", "CHN", "China", "Hubei", "Wuhan" ,30.5833, 114.2667) 3.2.3 在Google地图上显示查询结果上一节中,服务器端程序已经可以正常返回查询结果。本节将介绍如何从服务器取得该结果并显示在地图上。这一过程主要分为三个步骤:第一,需要获取服务器端的查询结果;第二,实现loadGeoInfo()接口;第三,显示查询结果等其他工作。 1.获取服务器端的查询结果本程序将使用流行的AJAX技术取得查询结果,一方面减少了流量,另一方面由于加载速度快,还可以增强用户体验。当然Google Maps API在这里也提供了相当便捷的使用AJAX的方法,完全没有必要使用其他AJAX的应用程序框架。在Google Maps API中,异步调用有两种方法,一种是使用GXmlHttp对象,另一种是使用GDownloadUrl()函数。 (1)使用GXmlHttp对象 创建GXmlHttp对象使用AJAX和直接创建XmlHttpRequest对象基本没有区别,使用十分灵活。由于Google封装时已经考虑到了浏览器的兼容性,所以GXmlHttp相对普通XmlHttpRequest的优势在于可以直接在不同浏览器上直接使用。目前GXmlHttp不仅可以支持Internet Explorer、Firefox和Opera等流行的浏览器,在Safari、Konquorer等用户群比较少的浏览器上都可以得到比较好的支持。下面仅给出一段模版形式的代码以供参考。 //创建GXmlHttp对象 var request = GXmlHttp.create();
//打开GXmlHttp,这里可以设置的参数有三个 //第一个参数:获取方法,判断是使用GET方法还是POST方法 //第二个参数:需获取的文件名 //第三个参数:获取模式,异步为真,同步为假 request.open("GET", "myfile.txt", true);
//回调函数,可以用function(){…}直接在此调用 //也可以预先定义函数XXX(),在此赋值 request.onreadystatechange=XXX request.onreadystatechange = function() { //判断状态,可根据不同的状态做不同的响应,本例中只捕捉了完全加载的状态4 if (request.readyState == 4) { alert(request.responseText); } }
//发送信息 request.send(null); (2)使用GDownloadUrl()函数 GDownloadUrl()函数应该说是一个简化版的异步处理函数,只能使用GET方法,不判断加载状态,只是在完全加载后调用回调函数。虽然GDownloadUrl()函数功能有限,但是因为其使用非常简单,所以应用也相当广泛。GDownloadUrl()调用方法如下。 GDownloadUrl(url, onload) 第一个参数即为需要用GET方法获取的URL,第二个参数onload是完全加载后的回调函数。 在本例IP地理位置可视化查询中,GDownloadUrl()已经完全符合要求,故在此使用GDownloadUrl()函数。获取服务器数据的函数getGeoInfo()如下。 function getGeoInfo(q) { //q为待查信息 GDownloadUrl("search.php?q="+q, function(data) { //直接用eval执行返回的Javascript字符串 eval(data); }); } 2.实现loadGeoInfo()接口loadGeoInfo()接口需要实现在Google地图上定位目标IP,添加信息窗口显示其详细信息等功能。在此仅给出接口部分的代码,如有疑问,请回顾本章上一节的内容。 function loadGeoInfo(q, ip, country_code, country_code3, country, region, city, latitude, longitude) { //新的信息窗口中的内容 var info = "<div align="left" style="overflow:X; font-size:12px">" + "<span style="font-size:14px"><strong>" + q + "</strong></span><br />" + "<strong>IP:</strong> " + ip + "<br />" + "<strong>国家:</strong> " + country + "<br />" + "<strong>代码:</strong> " + country_code + "(" + country_code3 + ")" + "<br />" + "<strong>省份:</strong> " + city + "<br />" + "<strong>城市:</strong> " + region + "<br />" + "<strong>经度:</strong> " + longitude + "<br />" + "<strong>纬度:</strong> " + latitude + "<br />" + "</div>"; //移动地图中心到新的位置 var point = new GLatLng(latitude, longitude); map.panTo(point); //如果创建了marker地标,则关闭当前的信息窗口并移除地标 if(marker) { map.closeInfoWindow(); map.removeOverlay(marker); } //创建新的地标 marker = new GMarker(point); map.addOverlay(marker); //显示信息窗口 marker.openInfoWindowHtml(info); } 最后为网页设计好LOGO,添加相应的事件响应(如搜索按钮单击,搜索栏回车等)就可以使用了。完整的首页index.html代码如下。 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8"/> <title>GeoIP 搜索者</title> <!--导入Google Maps API库文件。注意将本代码中的API Key替换为前文申请到的API Key--> <script src="http://maps.google.com/maps?file=api&v=2&key=ABQIAAAA1- j86tnUDFv8OAt C8dZVtKRT2yXp_ZAY8_ufC3CFXhHIE1NvwkxSzmwrQ90SNUILzGRpsBiaa860gfQ" type="text/javascript"></script> <script type="text/javascript"> //<![CDATA[ var map; //全局GMap2对象 var marker; //用于标识查询IP的GMarker地标 //初始化 function load() { if (GBrowserIsCompatible()) { map = new GMap2(document.getElementById("map")); map.setCenter(new GLatLng(39.92, 116.46), 2); //添加相应GControl()控件 map.addControl(new GSmallMapControl()); map.addControl(new GMapTypeControl()); //设定地图类型为混合地图 map.setMapType(G_HYBRID_MAP); //查询当前访客信息 getGeoInfo(""); } }
//响应查询栏的回车 //因为FireFox中event不是全局的,所以必须从相应DOM对象里传回event //如下写法可兼容IE和Firefox function pressEnter(event, q) { //如果输入了回车,则执行查询 if(event.keyCode==13 || event.keyCode==10) { getGeoInfo(q); } } //查询函数 function getGeoInfo(q) { GDownloadUrl("search.php?q="+q, function(data) { eval(data); }); }
//服务器端数据调用接口 function loadGeoInfo(q, ip, country_code, country_code3, country, region, city, latitude, longitude) { //新的信息窗口中的内容 var info = "<div align="left" style="overflow:X; font-size:12px">" + "<span style="font-size:14px"><strong>" + q + "</strong></span><br />" + "<strong>IP:</strong> " + ip + "<br />" + "<strong>国家:</strong> " + country + "<br />" + "<strong>代码:</strong> " + country_code + "(" + country_code3 + ")" + "<br />" + "<strong>省份:</strong> " + city + "<br />" + "<strong>城市:</strong> " + region + "<br />" + "<strong>经度:</strong> " + longitude + "<br />" + "<strong>纬度:</strong> " + latitude + "<br />" + "</div>"; //移动地图中心到新的位置 var point = new GLatLng(latitude, longitude); map.panTo(point); //如果创建了marker地标,则关闭当前的信息窗口并移除地标 if(marker) { map.closeInfoWindow(); map.removeOverlay(marker); } //创建新的地标 marker = new GMarker(point); map.addOverlay(marker); //显示信息窗口 marker.openInfoWindowHtml(info); } //]]> </script> <style> td{ text-align:center; } </style> </head> <body onload="load()" onunload="GUnload()"> <table cellSpacing="0" cellPadding="0" width="600" border="0" align="center"> <tbody> <tr> <td> <img src="geoipseeker.jpg" title="geoipseeker" alt="geoipseeker" width="450" height="50" style="boder:0" /> <td> </tr> <tr> <td height="25"> <!--此处onsubmit为"return false;"可防止表单提交,因为本例中用AJAX查询,无须提交表单--> <form onsubmit="return false;"> <!--分别为输入框和按钮都添加了事件监听。回车和点击按钮都可以进行查询--> <label for="q">在此输入IP或域名<input maxLength="50" size="25" name="q" id="q" onkeypress="pressEnter(event, this.value); " /> <input type="button" value="查找" id="search" onclick="getGeoInfo(q. value)" /></label> </form> </td> </tr> <tr> <td height="20" id="info"></td> </tr> <tr> <td> <div id="map" style="580px;height:350px"></div> </td> </tr> <tr> <td height="20" id="link"> <a href="http://blog.gmap2.net">Power by <strong>GMap2.net</strong></a> </td> </tr> </tbody> </table> </body> </html> 效果如图3.12所示。在此只讨论Google Maps API技术,没有对界面做过多设计,有兴趣的读者可自行设计一下界面。
图3.12 根据IP定位地理位置 |