如何在Google地圖上畫圓圈並且標繪行政區域

online
示意圖

在〈利用現成範本在Google地圖上畫圓圈〉的回應欄,曾郁翔先生問我如何將圓圈內的行政區著色。雖然作法不複雜,但解釋說明需要相當的篇幅,所以另外獨立成篇。

在Google Maps上查「新北市金山區」,會看到該行政區整個被著色;但Google Maps API 似乎尚未提供這個功能。

目前的可行作法是找出足以串連成區域邊界的點,將其經緯度數據載入Google Maps。其原理跟繪製多邊形是相同的,但具體方法有數種。此處僅介紹一種常見、易用的作法。整個過程可分為兩階段。

第一階段:尋找行政區邊界的經緯度資料

在data.gov.tw上可找得到鄉(鎮、市、區)行政區域界線,但其格式(SHP)不符合我們的需求。我們這裡要找的是GeoJSON格式的資料。幸好有善心的高手已將這類資料轉換成GeoJSON檔案,且在網路上公開分享,例如Ronny Wang建立的資料集(其原始資料是2011年版)。

在那個資料集中,每個鄉(鎮、市、區)各自獨立成一個檔案。建議您使用網頁上的「地圖瀏覽」頁籤來尋找所需的檔案。舉例而言,在該地圖上以滑鼠點擊新北市金山區,您可以看到其id為「19260611」。然後:

  • 或者,到「資料瀏覽」頁籤,按id編號找;
  • 或者,直接利用瀏覽器的網址列,在「鄉鎮市區行政區域界線」後面鍵入「/uri/19260611」。

開啟金山區的頁面後,點擊「GeoJSON」頁籤,即出現我們要找的資料。將之轉貼到「記事本」或Notepad++之類的編輯程式,然後轉檔,可命名為jinsan.jsondata.jsonxxx.json

不過,我們要做的更多:同時在地圖裡納入三芝。請按照上述的方法找到三芝的資料,但注意:別想把三芝的資料直接貼在金山的後面。這是因為每筆資料的結構可以比喻為「頭+身+尾」,而且每筆資料的頭、尾都一模一樣(可參閱kuro所寫的介紹)。所以兩個檔案結合在一起後應該是「頭+A身+B身+尾」。手動編輯比較麻煩,所以此處建議使用線上的混合器,例如Knowledge Walls的Online JSON Merger。(有些線上混合器的效果不佳,可能是因為無法處理漢字編碼)

將金山與三芝分別貼上去,然後在混合後的檔案開頭找到

["FeatureCollection","FeatureCollection"]

這是無謂的重複,所以要將之改成:

"FeatureCollection"

最後,跟處理單一行政區時一樣,將檔案儲存為jinsan_etc.json。以下範例所使用的json檔案就是這個。

若要納入石門、甚至再加上萬里…,則用同樣方法像滾雪球般地做。若覺得這太麻煩,或可嘗試Tom MacWright之geojson-merge(我還沒用過這程式,不敢保證。話說回來,會用它的人應該不需要讀我這一篇)。

此外,宜注意兩個技術限制:1) geoJSON檔案越大,載入所需時間越長;2) geoJSON檔案最好跟主網頁放在同一個網域。

第二階段:程式

此處不重複以前已介紹過的畫圓技術,僅將原版設定有所改變的部份以灰色襯底。

重點在第47-68行,其功用即在標繪行政區。內有逐行註解,我就不另外說明了。

	var g = google.maps;
	var map;
	var c= new g.LatLng(25.1942462, 121.5587476);//地圖中心座標(本版挪至陽明山)
	var z = 10;//地圖縮放比例(數值越大,細節越清楚;原版為7,此處調為10)
	var n = 4;//圓圈數
	var p = new Array();//圓心位置
	p[1] = new g.LatLng(25.286747, 121.587753);//圓心座標。以下同類各項視需要增刪
	p[2] = new g.LatLng(25.20267, 121.662555);
	p[3] = new g.LatLng(21.958092, 120.751612);
	p[4] = new g.LatLng(25.03861, 121.92417);
	var l = 30000;//半徑,單位為公尺
	var bc = '#000000';//圓周顏色
	var fc = '#FF8080';//圓內顏色(本版將顏色調淡)
	var ic = 'http://google-maps-icons.googlecode.com/files/radiation.png';//標誌圖案;若刪除,則恢復預設標誌
	var m = new Array();//標誌物件
	var r = new Array();//圓圈物件
	var t = new Array();//標誌說明文字
	t[1] = "台灣核一廠";//標誌說明文字內容。以下同類各項視需要增刪
	t[2] = "台灣核二廠";
	t[3] = "台灣核三廠";
	t[4] = "台灣核四廠";
	var wo = true;//是否顯示地圖說明視窗。true=是;false=否
	var wc = true;//是否自動關閉地圖說明視窗。true=是;false=否
	var wt = 5000;//自動關閉地圖說明視窗預設時間,計時單位為千分之一秒
	var mt = '台灣各核電廠周圍三十公里區域';//地圖總說明
	var w = new g.InfoWindow({
		content: mt, position: c,
        })
	function addCircles() {
    		for (i = 1; i <= n; i++) {
			m[i] = new g.Marker({map: map,position: p[i],icon: ic,zIndex: 80 - i,title: t[i]});
			r[i] = new g.Circle({map: map,radius: l,fillColor: fc,strokeWeight: 1,strokeColor: bc});
			r[i].bindTo('center', m[i], 'position');
		}	
	}
	function load() {
		var mapDiv = document.getElementById('map-canvas');
		map = new g.Map(mapDiv, {center: c,zoom: z,mapTypeId: g.MapTypeId.TERRAIN});
		x =addCircles();
		//載入geoJSON檔案
		map.data.loadGeoJson('jinsan_etc.json');
		//設定行政區之顯示格式
		map.data.setStyle(function(feature) {
			//此數列為預設顏色,共有十種
			var dc = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#e377c2', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf'];
			//讀取行政區之編號(Town_ID),且去除最後兩個0,例如將三芝6502100變為65021
			var i = (parseInt(feature.getProperty('Town_ID'))/100);
			//取 i 之個位數。以三芝為例,即為 1
			var j = i - (parseInt(i/10) * 10);
			//開始格式化
			return ({
			  //著色區域內部:三芝為dc[1],即#ff7f0e
			  fillColor: dc[j],
			  //以同樣顏色將邊界著色
			  strokeColor: dc[j],
			  //設定邊界粗細
			  strokeWeight: 1,
			  //將行政區提至最上層
			  zIndex: 300
			});
		});
		if(wo==true){
			w.open(map);
			if(wc==true){
				var o =setTimeout("w.close()",wt);
			}
		}
	}

結果:

按照這種作法,若要繪出圓圈內的行政區,就得先把它們一個個找出來。若利用jsts,或許可以將這個部份自動化。但這意味著還要先載入一些圓圈外的行政區資料。這會拖慢下載與程式執行的速度。既然我目前也沒時間去進一步探究,就把它擺著吧。

PS. 基於直覺的看法:在處理大量資料時,TopoJSON(可參考Taiwan.TopoJSON)之運作效率可能優於GeoJSON好。但我尚未仔細研究過。

發表迴響