Google Maps API地址轉換
8 |
將地址轉換成地理座標的程序被稱為地理編碼(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%; height: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)就可以囉~ 真是貼心。
執行結果如下:
Comments
# by 鳥毅
也是有不靠ashx的方法, 只是不見得比較好。 http://james.padolsey.com/javascript/cross-domain-requests-with-jquery/
# by 宅男嘿嘿
我記得一次要地址json資料不能超過10筆 原來還可以一個地址Ajax叫一次標記地圖一次 就可以達成標記點出現在地圖上10個點以上 學習了
# by Ammon
不需要透過 Server, Google map client api 本來就有提供地址查經緯度的功能 (v2, v3 都有) API文件 https://developers.google.com/maps/documentation/javascript/reference?hl=zh-TW#Geocoder 範例 https://google-developers.appspot.com/maps/documentation/javascript/examples/geocoding-simple?hl=zh-TW
# by Jeffrey
to Ammon, 原來有Client版,謝謝補充! 已在本文加註說明。
# by chester
你好.請問不用ashx,使用Ammon的方法,應該怎麼寫?
# by chester
我不是很懂jquery,能指導下嗎?不用ashx,使用Ammon的方法,應該怎麼寫?能不能再寫一篇博文呢.
# by Jeffrey
to chester, 範例來囉! 請參考: http://blog.darkthread.net/post-2012-06-21-js-google-maps-geocoder.aspx
# by mick
這樣子下就可以直接取得經緯度了 http://maps.google.com/maps/geo?output=csv&oe=utf-8&q=%E5%8F%B0%E5%8C%97%E5%B8%82%E9%87%8D%E6%85%B6%E5%8D%97%E8%B7%AF%E4%B8%80%E6%AE%B5122%E8%99%9F