终于有机会开始玩HTML5中行动装置GPS整合应用。 我的第一步是希望能在网页整合Google地图,即便实际需求不一定需要显示地图,但在开发测试阶段,要求开发人员直接由25.1234, 121.5678之类数字判断结果是否正确未免太不人道,因此用地图方式呈现特定经纬度资料是绝对必要的。第一个练习题就来试试在网页中显示特定经纬度的地图吧! Google地图Javascript API已经发展到了V3,整合应用起来相当方便省事,而官方的说明文件写得颇为详细(甚至有中文),要上手一点也不困难。以下便是我写的超简单的入门范例,试着在网页显示以政大校园八角亭为中心的地图。(八角亭是我每次猫空LSD的起点,经纬度数据在心跳表中唾手可得) <!DOCTYPE html> <html>…
Google Maps API地址转换
将地址转换成地理座标的程序被称为地理编码 (Geocoding),Google Maps API亦支援地理编码服务(注意:有每天查询次数不可超过2,500次的限制,申请Google Maps API Premier可以提高到100,000次) ,呼叫方法很简单,使用URL “http: //maps.googleapis.com/maps/api/geocode/json?address=要转换的地址&sensor=ture或false”,便可得到一份JSON格式的地址座标资讯,address参数除了完整地址,也可以输入一般性地名或片段地址,另外也能指定传回XML格式、使用语系、检视范围(优先列出该范围内符合结果,最经典的例子是在台湾查询”中正路”、”中山路”之类的菜市场路名,要指定县市范围才能找对目标)…等等,完整参数说明可参见API文件。
先用浏览器来个简单测试,在网址输入http://maps.googleapis.com/maps/api/geocode/json?address=%E5%8F%B0%E5%8C%97%E5%B7%BF %E5%85%89%E5%BE%A9%E5%8D%97%E8%B7%AF100%E8%99%9F&sensor=false (地址中文部分经encodeURI()编码),会得到以下结果:
{ "results" : [ { "address_components" : [ { "long_name" : "100", "short_name" : "100", "types" : [ "street_number" ] }, { "long_name" : "光復南路", "short_name" : "光復南路", "types" : [ "route" ] }, { "long_name" : "華聲里", "short_name" : "華聲里", "types" : [ "sublocality", "political" ] }, { "long_name" : "大安區", "short_name" : "大安區", "types" : [ "locality", "political" ] }, { "long_name" : "台北市", "short_name" : "北市", "types" : [ "administrative_area_level_2", "political" ] }, { "long_name" : "台灣", "short_name" : "TW", "types" : [ "country", "political" ] }, { "long_name" : "106", "short_name" : "106", "types" : [ "postal_code" ] } ], "formatted_address" : "106台灣台北市大安區光復南路100號", "geometry" : { "location" : { "lat" : 25.0438660, "lng" : 121.5563610 }, "location_type" : "ROOFTOP", "viewport" : { "northeast" : { "lat" : 25.04521498029150, "lng" : 121.5577099802915 }, "southwest" : { "lat" : 25.04251701970849, "lng" : 121.5550120197085 } } }, "partial_match" : true, "types" : [ "street_address" ] } ], "status" : "OK" }
Google Map很贴心地将地址拆解成国家、城市、行政区、路名、门牌,同时geometry中即为我们所要的经纬度座标值,另外也包含建议的地图检视范围。
虽然结果是JSON格式,但因为它是第三方网站且未支援JSONP整合,基于XMLHttpRequest不能跨网站呼叫的限制,我们无法直接在网页JavaScript中用$.ajax()之类的方式直接查询,我的解法是写一个极简单的ashx当成Proxy来克服: [ 2012-06-19更新 : Google Maps API另有提供从Javascript端进行地理编码的做法,可以全部在Client端处理完成,不需动用ashx,谢谢Ammon大人补充!]
<%@ WebHandler Language="C#" Class="Geocode" %> using System; using System.Web; public class Geocode : IHttpHandler { public void ProcessRequest (HttpContext context) { //注意: 如要防止被未授權對象將本程式當跳板對Google Maps API查詢, // 減損每日2,500次的額度,可再加入呼叫端檢核,此處省略以求單純 var wc = new System.Net.WebClient(); string address = context.Request["address"]; context.Response.BinaryWrite( wc.DownloadData( "http://maps.googleapis.com/maps/api/geocode/json?address=" + HttpUtility.UrlEncode(address) + "&sensor=false&language=zh-TW") ); } public bool IsReusable { get { return false; } } }
如此即可透过$.post(“Geocode.ashx”, { address: “查询地址” }, function(result) { … }, “json”);的方式进行地址转换,以下是一个应用范例,依据事件做好的地址清单,在地图显示台北市各消防分队的位置:
<!DOCTYPE html> <html> <head runat="server"> <title>Geocoding Test</title> <script type='text/javascript' src='http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.1.js'></script> <script src="https://maps.google.com/maps/api/js?sensor=true"></script> <meta name="viewport" content="initial-scale=1.0, user-scalable=no" /> <style> body,input { font-size: 9pt; } html { height: 100% } body { height: 100%; margin: 0px; padding: 0px } #map_canvas { height: 100% } </style> <script> $(function () { var markers = []; //呼叫ashx, 透過Google地理編碼將地址轉為經緯座標, 並傳回$.ajax() deferred function geocodeAjax(name, addr) { var ajax = $.post("Geocode.ashx", { address: addr }, function (r) { if (r.status == "OK") { var loc = r.results[0].geometry.location; markers.push({ title: name + "@" + addr, latlng: new google.maps.LatLng(loc.lat, loc.lng) }); } }, "json"); return ajax; } $.get("AddrList.txt", {}, function (list) { var lines = list.replace(/r/g, "").split('n'); var deferredArray = []; //lines[i]格式如下: //中正中隊-華山分隊,(02)23412668,中正區北平東路1號 for (var i = 0; i < lines.length; i++) { //lines.length; i++) { var parts = lines[i].split(','); //以AJAX進行地址轉換,並將$.ajax() deferred物件放入陣列中 deferredArray.push(geocodeAjax(parts[0], parts[2])); } //利用jQuery deferred特性,在所有地址轉換AJAX呼叫完畢後,執行特定動作 $.when.apply(null, deferredArray).then(function () { //設定地圖參數 var mapOptions = { mapTypeId: google.maps.MapTypeId.ROADMAP //正常2D道路模式 }; //在指定DOM元素中嵌入地圖 var map = new google.maps.Map( document.getElementById("map_canvas"), mapOptions); //使用LatLngBounds統計檢視範圍 var bounds = new google.maps.LatLngBounds(); //加入標示點(Marker) for (var i = 0; i < markers.length; i++) { var m = markers[i]; //將此座標納入檢視範圍 bounds.extend(m.latlng); var marker = new google.maps.Marker({ position: m.latlng, title: m.title, map: map }); } //調整檢視範圍 map.fitBounds(bounds); }); }); }); </script> </head> <body> <div id="map_canvas" style="width:100%;%20height:100%"></div> </body> </html>
程式不长,但有几个小地方值得补充:
- 消防分局地址被存在CSV档案中,用$.get()方式取回解析。
- 由于每一个地址会触发一次$.post()非同步呼叫,而地图显示要在所有非同步呼叫都取回结果后才执行。因此可善用jQuery 1.5+在$.ajax()加入的Deferred物件概念,利用$.when().then()等待所有$.ajax()呼叫都完成后才执行显示地图的逻辑。
- 由于$.when()的语法为$.when(ajax1, ajax2, ajax3),而我们的$.ajax()物件是保存在阵列中,难以一一列举成参数,此时apply()就派上用场啰!透过$.when.apply(null, ajaxArray).then(…)的写法,可达到跟$.when(ajax1, ajax2, ajax3, …)完全相同的结果。[补充: $.when.apply() 第一个参数该传$还是传null? ]
- 为了让地图缩放到能将所有标示点都包含在显示范围内,可使用map.fitBounds()方法,传入参数为google.maps.LatLngBounds()物件。原本我还自己逐一比对每一个座标的经纬度统计东西南北边界,后来才发现直接用LatLngBounds.extend(LatLng)就可以啰~ 真是贴心。
执行结果如下:
原文:http://blog.darkthread.net/post-2012-06-15-geocoding-api.aspx