前幾篇Google Map API文章,一直有用到Google地圖加上Marker的做法。(即下圖的紅色大頭針圖案)

不過,若全部的標示點用一樣的圖示,會顯得無趣且容易混淆(如下圖所示),雖然將滑鼠移到標示圖案上方會顯示名稱,在使用者體驗上總覺得還有改善空間。

事實上,Google Maps API在新增地標時,是可以自訂圖示的。MarkerOptions提供icon參數可指定圖檔URL,另外有shadow參點可指定陰影圖檔的URL,以取代預設的黑點紅色大頭針圖示。

只是有個小問題,在消防分隊位置顯示範例中,台北市共有44個消防分隊,即使能自訂圖示,可能也只是用44台消防車換掉44根大頭針,對於識別度並沒有太多提升,除非我們能預先製作44個刻有消防分隊名稱的圖檔,再一一對應到44個Marker使用...

身為程式魔人,44個圖檔用手工做是不被允許的,當然要自動產生才不會被人恥笑。我第一個想到是用ASP.NET動態產生圖檔的技巧,不過再轉念一想,何不用HTML5的Canvas來實做,完全在Client解決? (IE6/7/8: 那我們怎麼辦? 念你們曾縱橫江湖多年,也算時代英雄,你們自盡吧!)

HTML5 Canvas要即時產生圖檔不是問題,再配合Canvas.toDataURL()就可取代圖檔案URL,作為MarkerOptions.icon參數的設定值,就能達到當場動態產生Marker圖示的目的。

我試寫了一個小函數,用Canvas作圖,再用toDataURL()輸出作為<img>的src來源:

<!DOCTYPE html>
<html>
<head runat="server">
    <title>動態Canvas圖標</title>
    <script type='text/javascript' 
            src='http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.1.js'></script>   
    <style>
        body,input { font-size: 9pt; }
        img { margin: 5px }
    </style>
    <script>
        $(function () {
            //利用canvas產生一個內含文字的圖檔
            function createMarkerIcon(text, opt) {
                //定義預設參數
                var defaultOptions = {
                    fontStyle: "normal", //normal, bold, italic
                    fontName: "Arial",
                    fontSize: 12, //以Pixel為單位
                    bgColor: "darkblue",
                    fgColor: "white",
                    padding: 4,
                    arrowHeight: 6 //下方尖角高度
                };
                options = $.extend(defaultOptions, opt);
                //建立Canvas,開始幹活兒
                var canvas = document.createElement("canvas"),
                    context = canvas.getContext("2d");
                //評估文字尺寸
                var font = options.fontStyle + " " + options.fontSize + "px " + 
                           options.fontName;
                context.font = font;
                var metrics = context.measureText(text);
                //文字大小加上padding作為外部尺寸
                var w = metrics.width + options.padding * 2;
                //高度以Font的大小為準
                var h = options.fontSize + options.padding * 2 +
                        options.arrowHeight;
                canvas.width = w;
                canvas.height = h;
                //邊框及背景
                context.beginPath();
                context.rect(0, 0, w, h - options.arrowHeight);
                context.fillStyle = options.bgColor;
                context.fill();
                //畫出下方尖角
                context.beginPath();
                var x = w / 2, y = h, arwSz = options.arrowHeight;
                context.moveTo(x, y);
                context.lineTo(x - arwSz, y - arwSz);
                context.lineTo(x + arwSz, y - arwSz);
                context.lineTo(x, y);
                context.fill();
                //印出文字
                context.textAlign = "center";
                context.fillStyle = options.fgColor;
                context.font = font;
                context.fillText(text,
                    w / 2,
                    (h - options.arrowHeight) / 2 + options.padding);
                //傳回DataURI字串
                return canvas.toDataURL();
            }
 
            $("<img />", { src: createMarkerIcon("Jeffrey") })
            .appendTo("body");
            $("<img />", { src: createMarkerIcon("黑暗執行緒", {
                //注意: fontName所指定字型,需使用者機器上有安裝才算數
                fontName: "微軟正黑體", fontSize: 14, fontStyle: "bold",
                padding: 5, bgColor: "#6199DF", fgColor: "white"
            }) }).appendTo("body");
        });
    </script>
</head>
<body>
</body>
</html>

薑薑薑薑~~ 這樣子只要輸入文字,就可以當場產生圖示囉!

簡單修改程式,加入icon參數指向createMarkerIcon():

            var marker = new google.maps.Marker({
                position: latlng, //經緯度
                title: "八角亭", //顯示文字
                icon: createMarkerIcon("八角亭", {
                    bgColor: "brown"
                }),
                map: map //指定要放置的地圖對象
            });

原本黑頭大頭針就換成帶文字的告示牌,是不是清楚多了呢?

到了該為HTML5歡呼的時刻,讚啦!!


Comments

# by Vexed

http://blog.xuite.net/vexed/tech/59848483 滿類似的 :p

# by Jeffrey

to Vexed, 哈! 載入底圖這招省力又精美,學習了。謝謝分享~

# by 小黑

原來IE8 這麼不支援 html5 , 那使用 asp.net mvc 4 開發的網站,IE8 也不一定能正常顯示就對了!

# by Jeffrey

to 小黑,針對不支援HTML5的瀏覽器,有些技巧可以讓HTML5網頁盡可能正常顯示與運作。ASP.NET MVC 4雖然大量引用HTML5,但針對老瀏覽器仍透過Modernizr等外掛維持相容( http://blog.ericsk.org/archives/1444),預設的範本可以跨瀏覽器正常執行無誤。除非你加入了IE6/7/8不支援的程式碼,又不肯加入相容性處理(像本文用了Canvas就不顧老瀏死活是壞榜樣,請大家不要跟我做好朋友),才會出現問題。 所以預設ASP.NET MVC4可以跨瀏覽器,但一路開發下來要維持原則不崩壞,對開發者是項挑戰就是了 XD

# by 小黑

謝謝指導,看來真的是場硬仗了!

# by andrew

照你的方式做,可是出現的icon卻很小,連字都看不太清楚,請問這是什麼原因?

# by Jeffrey

to andrew, 由你的描述無法判斷問題,恐需要再提供程式碼範例等進一步資訊較能看出端倪。

# by 小張

黑暗執行緒您好,感謝你設計出這麼實用的canvas物件, 想請問是否可修改您的程式碼在我的作業上面,剛好在 研究一些關於Google map的東西,原本我是用markwithlabel 在icon上呈現姓名標幟,現在有了html5 canvas後其實只要 有姓名就可以了,相對更為實用。

# by 小張

黑暗執行緒您好,感謝你設計出這麼實用的canvas物件, 想請問是否可修改您的程式碼在我的作業上面,剛好在 研究一些關於Google map的東西,原本我是用markwithlabel 在icon上呈現姓名標幟,現在有了html5 canvas後其實只要 有姓名就可以了,相對更為實用。

# by Jeffrey

to 小張,歡迎引用,加註程式碼參考來源即可。

# by 小張

黑暗大您好,如果說在文字的部分希望能分成上下兩行,如上行是姓名,下行是身份證字號,該如何去設計,加了<br>標籤或把一開始的text改成html都沒有辦法,是否有解決的方案呢?

# by Jeffrey

to 小張,要印成兩行,可分兩次呼叫context.fillText(),透過Y座標參數控制上下位置即可。

# by 小張

黑暗大謝謝,已調整成功。

# by 小猴

你好請問一下:最後那個加入ICON 要如何使用 要加在哪 為什麼我加在後面都沒東西

# by Jeffrey

to 小猴,你是指icon: createMarkerIcon("八角亭", {bgColor: "brown"})嗎?icon原本可傳入png或gif URL指向圖示圖檔,我的程式改用HTML5當場繪製。你可先試試寫死URL看是否能正常顯示,若寫死URL可以換成createMarkerIcon()不行,有可能是createMarkerIcon裡的邏輯出了問題。

# by 小猴

不是 我意思是最後那個地圖加上八角亭的圖 怎麼做出來的? 因為我把上面的程式碼加在一起後,沒辦法呈現出來 可以讓我看一下那張圖得程式碼嗎(最後八角亭那張) 不好意思 我是新手 正在學寫地圖很爛 謝謝你黑暗大大

# by Jeffrey

to 小猴,我做了一個Live Demo: https://jsbin.com/fevuxi/1/edit?html,output 請參考

# by 小猴

謝謝你 解決了我的問題 感謝你耐心的指導~~

# by JING

黑暗大大你好 請問一下 那個最後八角停 因為顯示出來 會擋到地圖 想請問一下要如何使用透明元素 想呈現畫出來的背景是"半透明 文字不透明" 我嘗試過opacity=0.5但效果都沒出來 想請問一下大大能幫忙解決嗎?

# by Jeffrey

to JING, 試試marker.setOpacity(0.5);,參考:https://developers.google.com/maps/documentation/javascript/3.exp/reference#Marker

# by JING

黑暗大大 請問一下是要加在哪裡? 因為我剛看了參考網頁 再去網路找資訊還是無法了解要加在哪 var marker = new google.maps.Marker({ position: latlng, //經緯度 title: "八角亭", //顯示文字 icon: createMarkerIcon("八角亭", { bgColor: "brown" }), marker.setOpacity(0.5), map: map //指定要放置的地圖對象 }); 是加在這裏面嗎 嘗試很久還是跑不出來 希望大大能幫忙

# by Jeffrey

to JING, 要在marker物件建立後呼叫,也就是 var marker = new google.maps.Marker({...}); marker.setOpacity(0.5); 範例:https://jsbin.com/dafiji/edit?html,output

# by JING

謝謝大大指導~~~

Post a comment