Code is poetry.
可別以為我也在抓寶。敝人數月來日夜忙忙忙,無暇寫文;目前預計在接近年底時恢復發文。
這篇在計劃之外。它是為回應一位讀者的提問而寫,因需較大篇幅,所以獨立刊出。程式設計是這個部落格的附設攤位,我無意把它變成寫文的主線(況且,三十多年來,我在程式設計領域始終只是個業餘玩票的)。
該讀者希望以程式做到以下的效果:點選Google的My Map上的某地點,藉此在地圖上顯示該點半徑1000公尺內的其它My Map地點。本文先解決這個問題,然後順便介紹另一種功能:點選Google Maps上的任一個點,藉以顯示特定半徑內的My Map地點。為易於辨識,我將這兩種稱為「特定場址式」與「漫遊式」。
以下先呈現第一種,特定場址式。圖內是台灣全國的民航機場地點。若點選任一個地標,則可呈現半徑100公里內的其它機場。建議您先點選台南、台東:
第一階段:準備資料
就我以前所知,雖然我們可以利用Google Map API來顯示My Map的內容,但標記等等的經緯度資料其實是留在Google的伺服器,沒自動傳輸到終端機器(即用戶端)。掃描Google Maps API的說明、另迅速網搜一番,我未見改變。所以,我還是採用我所知的手動解決方式:將欲使用的My Map資料以KML格式匯出。如何匯出?請參閱Google的這篇說明。Google的My Map提供的匯出格式有KMZ與KML。KMZ較麻煩,我們在此不管它。
KML是XML家族的一員。我將下載的KML檔案之附檔名改為xml,免得未來的程式解讀出現問題。
然後,把這個KML檔案上傳到自己的網站。
第二階段:基本佈置
先將以下這一行放在網頁的body
區塊內。視您的需要而調整其中的style設定。
<div id="map-canvas" style="width: 640px; height: 540px; margin: 0; padding:0;"></div>
然後在head
區塊中加入這兩行:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script> <script type="text/javascript" src="http://maps.google.com/maps/api/js?key=AIzaSyBeA7WvHxLMHMKQ2Cn92Q2kNbQZ9U4E-xk&libraries=geometry"></script>
第1行是載入jQuery。網址可改為其它來源。
特別注意:將第2行內的YOUR_API_KEY
取代為您自己的API金鑰。若未有API金鑰,請向Google申請。只要有Google帳戶,即可免費取得,而且不到五分鐘就可搞定。請參閱Google的相關說明。
此外,請勿刪改YOUR_API_KEY
後面的&libraries=geometry
。其作用是加掛距離計算工具。
第三階段:程式
最後,在head
區塊中加入以下程式,並視需要改變設定,尤其是襯灰底的四行:
<script type="text/javascript"> //KML檔案(網址) var kmlFile = 'kml.xml'; var g = google.maps; var map, clickPoint, clickMarker, clickCircle; //地圖中心座標 var mapCenter = new g.LatLng(23.972583, 120.979361); //地圖縮放比例(數值越大,細節越清楚) var mapScale = 7; //搜尋半徑,單位為公尺 var rad = 100000; //以圓圈顯示搜尋範圍;true=是;false=否 var circleDisplay = true; //圓周顏色 var circleOutlineColor = '#c0c0c0'; //圓內顏色 var circleFillColor = '#e0e0e0'; //顯示點擊處;true=是;false=否 var clickPointDisplay = true; //目標資料、目標圖示 var topoMarker = []; //範圍外的目標圖案;若是ic='',則恢復Google Map預設標誌 var icon = 'https://www.google.com/mapfiles/marker_white.png'; //範圍內的目標圖案 var iconTarget = 'https://www.google.com/mapfiles/marker_green.png'; //點選的目標圖案 var iconCenter = 'https://www.google.com/mapfiles/marker_purple.png'; //是否顯示地圖說明視窗。true=是;false=否 var infoWindowDisplay = true; //是否自動關閉地圖說明視窗。true=是;false=否 var infoWindowAutoClose = true; //自動關閉地圖說明視窗預設時間;計時單位為千分之一秒 var infoWindowTimer = 3000; //地圖總說明文字 var infoWindowText = '請點觸任一標記'; var w = new g.InfoWindow({ content: infoWindowText, position: mapCenter, }) //讀取KML檔,製作地點標示,儲存為JSON function getKML(){ $.get(kmlFile, function(data){ //逐筆掃描地點資料 $(data).find("Placemark").each(function(q){ //取得地點名稱 name = $(this).find("name").text(); //取得地點座標名稱 coords = $(this).find("coordinates").text(); //解析座標經緯度 var c =[]; c = coords.split(","); lng = c[0]; lat = c[1]; //將經緯度設為Google Map Class topoc = new g.LatLng(lat, lng); //將資料儲存 as JSON var marker = new g.Marker({ map: map, position: topoc, icon: icon, title: name }); attachSensor(marker); //將此地標加入地標集合 topoMarker.push(marker); }); }); } //(重新)尋找地點,建立地標 function updateMarkers(){ //逐筆比較表列地點 for (var i = 0; i < topoMarker.length; i++) { //計算點擊處至第i筆地標的距離 var d = g.geometry.spherical.computeDistanceBetween(clickPoint, topoMarker[i].position); //依照範圍,改變地標的圖案 if (d < rad){ topoMarker[i].setIcon(iconTarget); } else{ topoMarker[i].setIcon(icon); } } } //(重新)繪製圓圈 function updateCircle(){ if (clickCircle){ clickCircle.setMap(null); } clickCircle = new g.Circle({ map: map, center: clickPoint, radius: rad, fillColor: circleFillColor, strokeWeight: 1, strokeColor: circleOutlineColor, zIndex: -100 }); } //(重新)繪製疊層 function updateGraph(){ //(重新)繪製圓圈 if (circleDisplay = true){ updateCircle(); } //(重新)尋找地點,建立地標 updateMarkers(); } //偵測點擊動作 function attachSensor(marker){ marker.addListener('click', function(e) { //取得被點擊的地標之經緯度 clickPoint = this.getPosition(); //(重新)繪製疊層 updateGraph(); //使被點擊的地標改變圖案 this.setIcon(iconCenter); }); } //啟動 $(document).ready(function(){ var mapDiv = document.getElementById('map-canvas'); //建立基本地圖 map = new g.Map(mapDiv, { center: mapCenter, zoom: mapScale, mapTypeId: g.MapTypeId.ROADMAP }); //讀取KML檔,儲存為JSON x = getKML(); //顯示地圖說明視窗 if(infoWindowDisplay==true){ w.open(map); if(infoWindowAutoClose==true){ var o =setTimeout("w.close()", infoWindowTimer); } } }); </script>
漫遊式
如何改變以上程式,讓地圖上的任意點都能被點選?我猜,將來必定有人會如此詢問。
我們只需改變第三階段的程式。以下的改變還更進一步:在載入地圖時先不秀出地點;在點擊地圖後,僅顯示半徑範圍內的地點。如果來自My Map的資料量龐大,這種作法有助於加速載入。
以下即其成果。請在台灣國境內任意點選:
幾乎跟剛才一樣,在head
區塊中加入以下程式,並視需要改變設定,尤其是襯灰底的五行:
<script type="text/javascript"> //KML檔案(網址) var kmlFile = 'kml.xml'; var g = google.maps; var map, clickPoint, clickMarker, clickCircle; //地圖中心座標 var mapCenter = new g.LatLng(23.972583, 120.979361); //地圖縮放比例(數值越大,細節越清楚) var mapScale = 7; //搜尋半徑,單位為公尺 var rad = 100000; //以圓圈顯示搜尋範圍;true=是;false=否 var circleDisplay = true; //圓周顏色 var circleOutlineColor = '#c0c0c0'; //圓內顏色 var circleFillColor = '#e0e0e0'; //顯示點擊處;true=是;false=否 var clickPointDisplay = true; //目標資料、目標圖示 var topo = [], topoMarker = []; //目標圖案;若是ic='',則恢復預設標誌 var ic = 'img/airport.png'; //是否顯示地圖說明視窗。true=是;false=否 var infoWindowDisplay = true; //是否自動關閉地圖說明視窗。true=是;false=否 var infoWindowAutoClose = true; //自動關閉地圖說明視窗預設時間;計時單位為千分之一秒 var infoWindowTimer = 3000; //地圖總說明文字 var infoWindowText = '請在台灣國境內任意點觸'; var w = new g.InfoWindow({ content: infoWindowText, position: mapCenter }); //讀取KML檔,儲存為JSON function getKML(){ $.get(kmlFile, function(data){ //逐筆掃描地點資料 $(data).find("Placemark").each(function(q){ //取得地點名稱 name = $(this).find("name").text(); //取得地點座標名稱 coords = $(this).find("coordinates").text(); //解析座標經緯度 var c =[]; c = coords.split(","); lng = c[0]; lat = c[1]; //將經緯度設為Google Map Class topoc = new g.LatLng(lat, lng); //將資料儲存 as JSON topo.push({ "name": name, "coord": topoc, }); }); }); } //(重新)尋找地點,建立地標 function updateMarkers(){ //清除先前出現的地標 if (topoMarker[0]){ for (var j = 0; j < topoMarker.length; j++) { topoMarker[j].setMap(null); } } //逐筆比較表列地點 for (var i = 0; i < topo.length; i++) { //計算點擊處至第i筆地標的距離 var d = g.geometry.spherical.computeDistanceBetween(clickPoint, topo[i].coord); //為範圍內之地標建立標記 if (d < rad){ var marker = new g.Marker({ map: map, position: topo[i].coord, icon: ic, title: topo[i].name }); //將此地標加入地標集合 topoMarker.push(marker); } } } //(重新)繪製圓圈 function updateCircle(){ if (clickCircle){ clickCircle.setMap(null); } clickCircle = new g.Circle({ map: map, center: clickPoint, radius: rad, fillColor: circleFillColor, strokeWeight: 1, strokeColor: circleOutlineColor, zIndex: -100 }); //讀取圓圈內被點擊的座標,並重新繪製疊層 clickCircle.addListener('click', function(event) { clickPoint = event.latLng; updateGraph(); }); } //繪製最新點擊處的標記 function updateClickMarker(){ if (clickMarker){ clickMarker.setMap(null); } clickMarker = new g.Marker({ map: map, position: clickPoint, //圖形,可自訂 icon: { path: g.SymbolPath.CIRCLE, strokeColor:'#9932CC', scale: 2 }, draggable: false, zIndex: -1 }); } //(重新)繪製疊層 function updateGraph(){ //(重新)繪製圓圈 if (circleDisplay = true){ updateCircle(); } //繪製最新點擊處的標記 if (clickPointDisplay = true){ updateClickMarker(); } //(重新)尋找地點,建立地標 updateMarkers(); } //啟動 $(document).ready(function(){ var mapDiv = document.getElementById('map-canvas'); //建立基本地圖 map = new g.Map(mapDiv, { center: mapCenter, zoom: mapScale, mapTypeId: g.MapTypeId.ROADMAP }); //讀取KML檔,儲存為JSON x = getKML(); //讀取地圖上被點擊的座標 map.addListener('click', function(event) { clickPoint = event.latLng; //(重新)繪製疊層 updateGraph(); }); //顯示地圖說明視窗 if(infoWindowDisplay==true){ w.open(map); if(infoWindowAutoClose==true){ var o =setTimeout("w.close()", infoWindowTimer); } } }); </script>
後話
從以上兩套程式,熟悉javascript者可輕易變化出其它的呈現方式。
礙於時間因素,我無法隨時解答讀者的問題,尤其是「另外要達成其它效果,要如何如何做」之類的詢問。這篇是今年內的最後一次此類答詢。請因此而失望的網友諒解、海涵!
將兩程式之「load」function 均改為jQuery的「document ready」模式。
請問上面您寫的那些程式是要寫在哪裡??
在head區塊。可能因為Google改變了服務條款,所以此處的內嵌無法顯示。敝人沒時間修改修理。抱歉!