Kadence

Google 地圖 ( 前後端實作 )

經過上一篇的洗禮之後,對於地圖控制應該都能掌握了,再來就要進入「實戰」的部分,在地圖的程式裡頭加入 Webduino 的程式碼,就可以在地圖裡顯示燈泡狀態。

更換 Marker 圖示

因為要顯示燈泡訊息,所以我們要預備兩張燈泡圖片給 Marker 使用 ( 一張開燈,一張關燈 ),這樣在地圖上才會透過 Marker ,清楚看到這個地點的燈泡是亮還是暗,然後記得圖片要用成背景透明的 webp 檔案。

燈泡圖示

沿用上一篇最後一個範例的程式碼,如果更換 Marker 圖片,只需要在 Marker 的設定檔多增加一個 icon 的屬性,然後把圖片網址放進去,我這邊預設放入的圖片是「關燈」的圖片。

var marker_config = [{
  address: '總統府',
  icon:'http://example.oxxostudio.tw/it2016/it2016-day22-off.webp'
}];

然後記得更換玩,在下方的屬性也要加進去 icon 屬性

marker_config.forEach(function(e,i){
  _geocoder(e.address,function(address){
    //下方新增 icon 屬性
    var marker = {
      position:address,
      map:map,
      icon:e.icon
    }
    markers[i] = new google.maps.Marker(marker);
    markers[i].setMap(map);
    markers[i].addListener('click', function() {
      infoWindows[i].open(map, markers[i]);
    });
  });
});

執行網頁應該就可以看到一個燈泡圖案出現在地圖上了。

關燈燈泡圖案顯示在地圖上

只有這樣還不夠,如果我們希望觸發某個事件之後就更換燈泡圖案,該怎麼做呢?這時候我們就會需要用到 .setIcon 這個方法,下面這段程式,表示在兩秒之後,會把地圖上的燈泡從熄滅換成打開。

setTimeout(function(){
  markers[0].setIcon('http://example.oxxostudio.tw/it2016/it2016-day22-on.webp');
},2000);

不過其實這樣還是會有點風險,因為我們不能確保兩秒後 marker 的初始化已經完成,所以最好的做法是在初始化 marker 的當下,就進行 setIcon 的動作,下面這段程式表示當我們打開網頁,Marker 初始化完成之後,就會自動變成開燈的圖案。( 如果你有許多的 marker 也是一樣,要確保所有 marker 都初始化完成才能繼續 )

marker_config.forEach(function(e,i){
  _geocoder(e.address,function(address){
    var marker = {
      position:address,
      map:map,
      icon:e.icon
    };
    markers[i] = new google.maps.Marker(marker);
    markers[i].setMap(map);
    markers[i].addListener('click', function() {
      infoWindows[i].open(map, markers[i]);
    });
    _setIcon(markers[i],'http://example.oxxostudio.tw/it2016/it2016-day22-on.webp');
  });
});

function _setIcon(e,icon){
  e.setIcon(icon);
}

開燈燈泡顯示在地圖上

編輯資訊視窗 Info Window 內容

接著想要在資訊視窗 info window 裡面顯示「地點名稱」、「時間」,並放入一個按鈕,這樣如果不點選燈泡圖案開關的話,點選這個按鈕一樣可以作切換,所以這邊也需要另外兩張按鈕的圖片,一張打開,一張關起來,這邊就不一定要 webp 了,用 webp 也可以,而且也不見得要「兩張」,如果會寫 CSS 的話,做在同一張然後用 background-position 切換就可以。

切換開關按鈕

要編輯資訊視窗內容可以編輯前一個範例的資訊視窗設定檔 info_config,內容基本上就是純 HTML,這邊我一樣用一個 h2 放地點位置,span 顯示現在時間,img 則是開關圖片,兩者都可以透過 CSS 來進行設定。

var info_config = [
  '<div id="infoDiv1" class=”infoDiv>'+
  '  <h2>總統府</h2>'+
  '  <span></span><br/>'+
  '  <div></div>'+
  '</div>'
];

新增加兩個流程,一個是資訊視窗內容的流程,另外一個則是獲取當前時間的流程,這邊透過 setInterval 每秒獲取新的時間來做改變,然而為了因應多個 Marker,所以我們在顯示的名稱上勢必要有所區隔,這裡在每個顯示 div 的後方都加個數字,這些數字是由 Marker 產生,所以可以確定每個資訊視窗都會對應到自己的 Marker。

function _content(e){
  setInterval(function(){
    $('#infoDiv'+e+' span').text(getTime());
  },1000);
}

function getTime() {
  var date = new Date();
  var h = date.getHours();
  var m = date.getMinutes();
  var s = date.getSeconds();
  if (h < 10) {
    h = '0' + h;
  }
  if (m < 10) {
    m = '0' + m;
  }
  if (s < 10) {
    s = '0' + s;
  }
  var now = h + ':' + m + ':' + s;
  return now;
}

記得可以透過 google.maps.event.addListener 監聽資訊視窗的內容是否準備好,如果準備好,就綁定對應的流程。

//設定資訊視窗內容
info_config.forEach(function(e,i){
  infoWindows[i] = new google.maps.InfoWindow({
    content: e
  });
  //設定監聽事件,在資訊視窗準備好之後,就綁定對應的流程
  google.maps.event.addListener(infoWindows[i], 'domready', function() {
    _content(i);
  });
});

資訊與燈泡狀態顯示在地圖上

按鈕與 Marker 連動

為了讓資訊視窗內的按鈕可以和 Marker 連動,這邊多做了一些修改,由 s 變數判斷是否按下開關,並且決定開關的背景圖片位置 ( 看起來就會是打開和關起來 ),然後透過 _setIcon 來更改 Marker 的樣子。

function _content(e){
  timer[e] = setInterval(function(){
    $('#infoDiv'+e+' span').text(getTime());  //顯示當下時間
  },1000);
  $('#infoDiv'+e+' div').on('click',function(){
    s = s * -1;
    if(s<0){
      $(this).css({
        'background-position':'-70px 0'
      });
      _setIcon(markers[e],'http://example.oxxostudio.tw/it2016/it2016-day22-off.webp');
    }else{
      $(this).css({
        'background-position':'0 0'
      });
      _setIcon(markers[e],'http://example.oxxostudio.tw/it2016/it2016-day22-on.webp');
    }
  });
}

因為一開始就考量到多個 Marker 的影響,所以如果你有兩個 Marker,基本上執行之後也會是正常運作的。

多個燈泡圖示顯示在地圖上

加入 Webduino 與真正的燈泡連動

首先一樣在 HTML 的 head 要引入對應的 JavaScript

<script src="https://code.jquery.com/jquery-1.9.1.js"></script>
<script src="https://webduino.io/components/webduino-js/dist/webduino-all.min.js"></script>
<script src="https://blockly.webduino.io/webduino-blockly.js"></script>

JavaScript 一開始先多兩個全域變數,因為有些流程是自己獨立,用全域比較不會有問題,led 宣告為陣列,因為 Marker 不只一個,要指定對應的 Marker 給對應的 led,boardNum 則是用來判斷板子是否都上線。

var led = [];
var boardNum = 0;

再來就是把 boardReady 在地圖開始之後在執行,避免裝置沒上線地圖也出不來的窘境,然後把剛剛內容的流程獨立成一個 ready 的流程。

//使用地址或名稱標出起始中心點位置
_geocoder('總統府',function(address){
  map = new google.maps.Map(document.getElementById('map'), {
    center: address,
    zoom: 14
  });

  //設定資訊視窗內容
  info_config.forEach(function(e,i){
    infoWindows[i] = new google.maps.InfoWindow({
      content: e
    });
    //設定監聽事件,在資訊視窗準備好之後,就綁定對應的流程
    google.maps.event.addListener(infoWindows[i], 'domready', function() {
      _content(i);
    });
  });

  //設定開發版裝置連線
  boardReady('第一塊裝置 ID', function (board) {
    board.systemReset();
    board.samplingInterval = 250;
    led[0] = getLed(board, 10); //設定第一塊裝置的電燈接腳
    boardNum = boardNum + 1;
    if(boardNum===2){
      ready(); //裝置都上線後執行
    }
  });

  boardReady('第二塊裝置 ID', function (board) {
    board.systemReset();
    board.samplingInterval = 250;
    led[1] = getLed(board, 10); //設定第二塊裝置的電燈接腳
    boardNum = boardNum + 1;
    if(boardNum===2){
      ready(); //裝置都上線後執行
    }
  });
});

裝置都上線之後要執行的 ready 流程長這樣,在上線之後再定義 marker。

//開發裝置連線後要執行
function ready(){
  //定義各個marker
  marker_config.forEach(function(e,i){
    _geocoder(e.address,function(address){
      var marker = {
        position:address,
        map:map,
        icon:e.icon
      };
      //把每個 marker 按照設定檔標記到地圖上
      markers[i] = new google.maps.Marker(marker);
      markers[i].setMap(map);
      //綁定每個 marker 的點擊事件
      markers[i].addListener('click', function() {
        infoWindows[i].open(map, markers[i]);
      });
    });
  });
}

最後不要忘記在點選開關的程式,要加入 led.onled.off 的指令,這樣才能夠真正控制電燈。

//設定 marker 圖案
function _setIcon(e,icon){
  e.setIcon(icon);
}

function _content(e){
  //顯示時間 ( 每秒變動 )
  setInterval(function(){
    $('#infoDiv'+e+' span').text(getTime());
  },1000);
  $('#infoDiv'+e+' div').on('click',function(){
    s = s * -1;
    if(s<0){
      led[e].off();  //關閉電燈
      $(this).css({
        'background-position':'-70px 0'
      });
      //更換 marker 圖案為關燈
      _setIcon(markers[e],'http://example.oxxostudio.tw/it2016/it2016-day22-off.webp');
    }else{
      led[e].on();  //點亮電燈
      $(this).css({
        'background-position':'0 0'
      });
      //更換 marker 圖案為開燈
      _setIcon(markers[e],'http://example.oxxostudio.tw/it2016/it2016-day22-on.webp');
    }
  });
}

到這邊執行網頁後,我們就可以在地圖上操控電燈,地圖上也會顯示電燈目前的狀態囉!

地圖顯示智慧插座電燈目前狀態

小結

其實 Google 提供了相當多好用的服務給我們使用,如果能夠熟悉這些網路服務,相信跟生活周遭用品結合,才是真正「物聯網」的精髓吧!

最後,Google Maps 的 API 非常有用,一定要好好閱讀的。

https://developers.google.com/maps/documentation/javascript/reference?hl=zh-tw#InfoWindow

分享