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>

程式不长,但有几个小地方值得补充:

  1. 消防分局地址被存在CSV档案中,用$.get()方式取回解析。
  2. 由于每一个地址会触发一次$.post()非同步呼叫,而地图显示要在所有非同步呼叫都取回结果后才执行。因此可善用jQuery 1.5+在$.ajax()加入的Deferred物件概念,利用$.when().then()等待所有$.ajax()呼叫都完成后才执行显示地图的逻辑。
  3. 由于$.when()的语法为$.when(ajax1, ajax2, ajax3),而我们的$.ajax()物件是保存在阵列中,难以一一列举成参数,此时apply()就派上用场啰!透过$.when.apply(null, ajaxArray).then(…)的写法,可达到跟$.when(ajax1, ajax2, ajax3, …)完全相同的结果。[补充: $.when.apply() 第一个参数该传$还是传null? ]
  4. 为了让地图缩放到能将所有标示点都包含在显示范围内,可使用map.fitBounds()方法,传入参数为google.maps.LatLngBounds()物件。原本我还自己逐一比对每一个座标的经纬度统计东西南北边界,后来才发现直接用LatLngBounds.extend(LatLng)就可以啰~ 真是贴心。

执行结果如下:

原文:http://blog.darkthread.net/post-2012-06-15-geocoding-api.aspx

Leave a Reply