DEV Community

Cover image for Google Maps API 學習筆記 – 1:地圖、標記、客製樣式
Let's Write
Let's Write

Posted on • Originally published at letswrite.tw

3 3

Google Maps API 學習筆記 – 1:地圖、標記、客製樣式

本篇大綱:


原來 Google Maps API 有每個月的免費額度

之前聽到 Google Maps API 要錢,就一直沒想碰這塊,最近朋友在問,回頭去看了一下定價後,才發現官網上面寫每個月有 200 美金的免費額度。

照免費的額度表上看,因為這篇會用到的是「Maps JavaScript API」,是屬於「Dynamic Maps」這個,每個月可以載入 28000 次,超過才開始計價。

28000 次耶,一般頁面正常使用,那個流量都可以放個廣告了吧。覺得自己平常在玩的小頁面不會到這個量,就想來學一下。

Google Maps API 定價表

Google Maps API 定價表:https://cloud.google.com/maps-platform/pricing/sheet/?hl=zh-tw

這篇筆記主要想寫的,是文件上從「Get an API Key」,一直看到「Interacting with the Map」這邊的學習記錄,一邊看一邊實作。

因為想要記下的是以後會常用到的功能,所以想看所有 Google Maps API 的話,可以直接看官方文件。

本篇參考、使用資源如下:

  1. 官網 Maps Javascript API 文件
  2. JS Framework:Vue.js
  3. CSS Framework:Bootstrap4
  4. Icon:Eva Icons

申請 Google Maps API Key

API Key 要從 Google Cloud Platform(GCP) 的後台申請。

這邊新開一個專案來用,填寫完專案名稱後按確認,GCP 就會執行開新專案的動作。

GCP上建立新專案

建立完後,點選剛新增的專案,就會列出跟 Maps 相關的一堆 API:

Map 相關 API

這邊用的是 Maps Javascript API,點進去後再點啟用,GCP 就可以看到了:

確認 Maps JavaScript API 是啟用的

確認完要用的 API 是啟用狀態後,接著生成一組 API Key,以及對這組 API Key 設定限制,防止有人看到你的 API Key 後,拿去偷用。

直接點啟用 API 那邊的項目,再點憑證,或是直接進入 GCP 憑證頁面 後,就會看見要申請的區塊:

點選 API 金鑰

點選 API 金鑰,會出現一個金鑰的 Key,把值 copy 下來,之後頁面在引用 JS 時,就是這樣:

<script async defer  
  src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY">  
</script>
Enter fullscreen mode Exit fullscreen mode

取得API Key後,點選「限制金鑰」

GCP 很貼心的提醒要為金鑰增加限制,點選「限制金鑰」後,會看見 4 種限制方式:

API Key的4種限制方式

要用哪一種就看各人了。

官方文件連結:Get an API Key


開始使用 Maps Javascript API

新增一個 HTML 的頁面,引用 Map JS:

<script async defer src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap"</script>
Enter fullscreen mode Exit fullscreen mode

這邊因為有用Vue.js,所以會把後面的「callback=initMap」刪掉,改成 window load 完後再執行 initMap()

以下寫的 code,很偷懶的直接用 ES6,如果要支援度高一點的話,請自行編譯成 ES5

初始化地圖(initMap)的 function,做 2 件事:

  1. 建立地圖,設定地圖中心點
  2. 放置標記(marker)

HTML 如下(CSS Framework:Bootstrap4):

<div id="app" class="container">  
  <div class="row">  
    <div class="col">  
      <div id="map" class="embed-responsive embed-responsive-16by9"></div>  
    </div>  
  </div>  
</div>
Enter fullscreen mode Exit fullscreen mode

JS 如下:

const googleMap = new Vue({
el: '#app',
data: {
map: null
},
methods: {
// init google map
initMap() {
// 預設顯示的地點:台北市政府親子劇場
let location = {
lat: 25.0374865, // 經度
lng: 121.5647688 // 緯度
};
// 建立地圖
this.map = new google.maps.Map(document.getElementById('map'), {
center: location, // 中心點座標
zoom: 16, // 1-20,數字愈大,地圖愈細:1是世界地圖,20就會到街道
/*
roadmap 顯示默認道路地圖視圖。
satellite 顯示 Google 地球衛星圖像。
hybrid 顯示正常和衛星視圖的混合。
terrain 顯示基於地形信息的物理地圖。
*/
mapTypeId: 'terrain'
});
// 放置marker
let marker = new google.maps.Marker({
position: location,
map: this.map
});
}
},
created() {
window.addEventListener('load', () => {
this.initMap();
});
}
});

開啟頁面後,就會看到如下:

基本 Google Maps

對,看起來就跟一般我們在用 Google Maps 差不多,畢竟只用了基本功能,想有些不一樣就要再加一些code。


放置多個標記

要放多個標記前,當然可以直接在頁面上加一個陣列,然後資料全寫在裡面。

但 Google 文件上在這部份用了比較好的做法,就是把資料收集在一個檔案上,然後 GET 進來。

之所以會寫這點,是因為他的檔案不是常見的 xxx.json,而是 xxx.geojson。

看了文件後才知道有 geojson 這格式,維基百科 上寫是拿來記錄地理訊息的 JSON。

參考 Google 文件的範例,把幾個點的位置存成了 map.geojson,如下:

{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [25.0374865, 121.5647688]
},
"properties": {
"id": 1,
"name": "臺北市政府親子劇場",
"site": "110台北市信義區市府路1號市府大樓"
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [25.0397146, 121.5653771]
},
"properties": {
"id": 2,
"name": "誠品信義店",
"site": "110台北市信義區松高路11號"
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [25.0405919, 121.5647644]
},
"properties": {
"id": 3,
"name": "蔦屋書店信義店",
"site": "110台北市信義區忠孝東路五段8號"
}
}
]
}

geometry 裡的經緯度必填。

properties,看示範時很像是可以填自己客製的東西,就拿來填名稱跟地址,之後在 info windows 時會用到。

有了各地點的檔案後,就是用回圈把每個地點設成 marker,JS 如下:

const googleMap = new Vue({
el: '#app',
data: {
map: null
},
methods: {
// init google map
initMap() {
// 預設顯示的地點:台北市政府親子劇場
let location = {
lat: 25.0374865, // 經度
lng: 121.5647688 // 緯度
};
// 建立地圖
this.map = new google.maps.Map(document.getElementById('map'), {
center: location,
zoom: 16,
mapTypeId: 'terrain'
});
// 放置多個marker
fetch('./map.geojson')
.then(results => results.json())
.then(result => {
let res = result.features;
Array.prototype.forEach.call(res, r => {
let latLng = new google.maps.LatLng(r.geometry.coordinates[0], r.geometry.coordinates[1]);
let marker = new google.maps.Marker({
position: latLng,
map: this.map
});
});
});
}
},
created() {
window.addEventListener('load', () => {
this.initMap();
});
}
});

開啟頁面後,看到如下:

多個標記的 Google Maps

對,看上去也跟用 My Map 的功能一樣,沒什麼厲害的。畢竟這也只是在基本的地圖上,多放了幾個標記而已。

接下來再加上可以用的參數,看上去就會不一樣了。


標記(Marker)客製化

對網頁上要放 Google Maps 來說,最會被企畫問到的問題就是:「標記的圖案可以改嗎?」、「標記可以改成公司的 Logo 嗎?」

Google Maps API 有提供修改標記的參數,主要有 2 個可以用:

  1. 用 Google 預設的 4 種圖案
  2. 直接讀一個圖檔

Google 預設的 4 種圖案

使用的方式很簡單,在 JS 裡,marker 的部份改成以下:

var marker = new google.maps.Marker({  
  position: latLng,  
  icon: {  
    path: google.maps.SymbolPath.CIRCLE,  
    scale: 10  
  },  
  map: map  
});
Enter fullscreen mode Exit fullscreen mode

icon.path,有 4 種可用,顯示出來的樣式如 Google 提供的圖表:

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

直接讀一個圖檔

直接讀一個圖檔,是更能達成企畫需求的方式,code 如下:

var marker = new google.maps.Marker({  
  position: latLng,  
  icon: '圖檔的絕對路徑.png',  
  map: map  
});
Enter fullscreen mode Exit fullscreen mode

code 很簡單,icon 的值直接寫入圖檔路徑就行。

這邊直接在 Eva Icons 上抓一張圖來用,code 如下:

const googleMap = new Vue({
el: '#app',
data: {
map: null
},
methods: {
// init google map
initMap() {
// 預設顯示的地點:台北市政府親子劇場
let location = {
lat: 25.0374865, // 經度
lng: 121.5647688 // 緯度
};
// 建立地圖
this.map = new google.maps.Map(document.getElementById('map'), {
center: location,
zoom: 16,
mapTypeId: 'terrain'
});
// 放置多個marker
fetch('./map.geojson')
.then(results => results.json())
.then(result => {
let res = result.features;
Array.prototype.forEach.call(res, r => {
let latLng = new google.maps.LatLng(r.geometry.coordinates[0], r.geometry.coordinates[1]);
let marker = new google.maps.Marker({
position: latLng,
icon: "https://akveo.github.io/eva-icons/outline/png/128/gift-outline.png", // 自定義圖標,google也有提供5個預設的圖標,參考下方*1
map: this.map
});
/* *1 用google預設圖標的寫法
let marker = new google.maps.Marker({
icon: {
// icon圖例:https://developers.google.com/maps/documentation/javascript/symbols?hl=zh-tw#predefined
// google.maps.SymbolPath.CIRCLE
// google.maps.SymbolPath.BACKWARD_CLOSED_ARROW
// google.maps.SymbolPath.FORWARD_CLOSED_ARROW
// google.maps.SymbolPath.BACKWARD_OPEN_ARROW
// google.maps.SymbolPath.FORWARD_OPEN_ARROW
path: google.maps.SymbolPath.CIRCLE,
scale: 10
}
});
*/
});
});
}
},
created() {
window.addEventListener('load', () => {
this.initMap();
});
}
});

marker 的另外 2 個參數

「標記」除了自定 icon,還有另外 2 個參數可以用:

  1. 可否拖拉
  2. 動態效果

可否拖拉的參數是「draggable」,動態效果是「animation」:

let marker = new google.maps.Marker({  
  position: latLng,  
  map: this.map,  
  animation: google.maps.Animation.DROP, // DROP掉下來、BOUNCE一直彈跳
  draggable: false // true、false可否拖拉  
});
Enter fullscreen mode Exit fullscreen mode

這一階段會看到的結果如下:

標記(Marker)客製化


Info Windows

在標記上,還有一個很特別的功能,是要用 API 才會有的,就是 Info Windows,資訊小視窗,樣式如下:

Google Maps Info Windows

官方文件中,Info Windows 有專門的一頁在介紹,這邊就直接製作一個範例出來。(Info Windows 官方文件)

在用回圈寫入 marker 時,就要把 Info Windows 加進去,並監聽 click 事件。

這邊的範例再特別寫入一個,由外面的按鈕讓 marker 上的 Info Windows 被打開,而不是一定要點到 marker 才會打開。

HTML 用 v-for 為每個地點加入一個按鈕,並加上 @click 事件:

<button type="button" class="btn btn-outline-secondary"  
  v-for="f in features" @click="openInfoWindows(f.properties.id)"  
>  
  {{ f.properties.name }}  
</button>
Enter fullscreen mode Exit fullscreen mode

JS 部份如下:

const googleMap = new Vue({
el: '#app',
data: {
map: null,
features: [], // 存入每一個地點
infowindowAll: {} // 存入每一個marker上的info windows
},
methods: {
// init google map
initMap() {
// 預設顯示的地點:台北市政府親子劇場
let location = {
lat: 25.0374865, // 經度
lng: 121.5647688 // 緯度
};
// 建立地圖
this.map = new google.maps.Map(document.getElementById('map'), {
center: location,
zoom: 16,
mapTypeId: 'terrain'
});
// 放置多個marker
fetch('./map.geojson')
.then(results => results.json())
.then(result => {
this.features = result.features;
Array.prototype.forEach.call(this.features, r => {
let latLng = new google.maps.LatLng(r.geometry.coordinates[0], r.geometry.coordinates[1]);
let marker = new google.maps.Marker({
position: latLng,
map: this.map
});
// info window
let infowindow = new google.maps.InfoWindow({
content: `<h6>${r.properties.name}</h6>` // 支援html
});
// 監聽 marker click 事件
marker.addListener('click', e => {
infowindow.open(this.map, marker);
});
// 加一個open的method,就可由外部按鈕開啟
this.infowindowAll[r.properties.id] = {
open: function() {
infowindow.open(this.map, marker);
}
};
});
});
},
// 由外部按鈕開啟info windows
openInfoWindows(id) {
this.infowindowAll[id].open();
}
},
created() {
window.addEventListener('load', () => {
this.initMap();
});
}
});

看到的頁面截圖如下:

Google Maps Info Windows


客製化地圖樣式

API 除了可以做出客製標記(marker)、Info Windows 外,還可以做一個直接用 Google Maps 做不到的事,就是客製化地圖樣式。

這邊直接參考文件的示範,寫一個 night mode 的地圖,跟隱藏商家的地圖。

night mode 地圖

在一開始建立地圖,也就是以下這段的時候:

this.map = new google.maps.Map(document.getElementById('map'), {  
  center: location,  
  zoom: 16,  
  mapTypeId: 'terrain',  
  styles: [ /* ... */ ] // 這邊可加入客製 style  
});
Enter fullscreen mode Exit fullscreen mode

就可以加入一個「styles」的參數,把想要的樣式給加進去了。當然,加的規則必須照著文件上的寫。(Start Styling your Map 官方文件)

這邊寫一個可以用 toggle 按鈕,切換日間 / 夜間地圖的範例:

const googleMap = new Vue({
el: '#app',
data: {
map: null,
nightMode: 'close', // 夜間模式:open開啟
// 夜間模式的styles,資料來源:https://developers.google.com/maps/documentation/javascript/styling?hl=zh-tw
nightModeStyles: [
{ elementType: 'geometry', stylers: [{color: '#242f3e'}] },
{ elementType: 'labels.text.stroke', stylers: [{color: '#242f3e'}] },
{ elementType: 'labels.text.fill', stylers: [{color: '#746855'}] },
{
featureType: 'administrative.locality',
elementType: 'labels.text.fill',
stylers: [{color: '#d59563'}]
},
{
featureType: 'poi',
elementType: 'labels.text.fill',
stylers: [{color: '#d59563'}]
},
{
featureType: 'poi.park',
elementType: 'geometry',
stylers: [{color: '#263c3f'}]
},
{
featureType: 'poi.park',
elementType: 'labels.text.fill',
stylers: [{color: '#6b9a76'}]
},
{
featureType: 'road',
elementType: 'geometry',
stylers: [{color: '#38414e'}]
},
{
featureType: 'road',
elementType: 'geometry.stroke',
stylers: [{color: '#212a37'}]
},
{
featureType: 'road',
elementType: 'labels.text.fill',
stylers: [{color: '#9ca5b3'}]
},
{
featureType: 'road.highway',
elementType: 'geometry',
stylers: [{color: '#746855'}]
},
{
featureType: 'road.highway',
elementType: 'geometry.stroke',
stylers: [{color: '#1f2835'}]
},
{
featureType: 'road.highway',
elementType: 'labels.text.fill',
stylers: [{color: '#f3d19c'}]
},
{
featureType: 'transit',
elementType: 'geometry',
stylers: [{color: '#2f3948'}]
},
{
featureType: 'transit.station',
elementType: 'labels.text.fill',
stylers: [{color: '#d59563'}]
},
{
featureType: 'water',
elementType: 'geometry',
stylers: [{color: '#17263c'}]
},
{
featureType: 'water',
elementType: 'labels.text.fill',
stylers: [{color: '#515c6d'}]
},
{
featureType: 'water',
elementType: 'labels.text.stroke',
stylers: [{color: '#17263c'}]
}
]
},
methods: {
// init google map
initMap() {
// 預設顯示的地點:台北市政府親子劇場
let location = {
lat: 25.0374865, // 經度
lng: 121.5647688 // 緯度
};
// 建立地圖
this.map = new google.maps.Map(document.getElementById('map'), {
center: location,
zoom: 16,
mapTypeId: 'terrain'
});
// 放置多個marker
fetch('./map.geojson')
.then(results => results.json())
.then(result => {
this.features = result.features;
Array.prototype.forEach.call(this.features, r => {
let latLng = new google.maps.LatLng(r.geometry.coordinates[0], r.geometry.coordinates[1]);
let marker = new google.maps.Marker({
position: latLng,
map: this.map
});
});
});
}
},
created() {
window.addEventListener('load', () => {
this.initMap();
});
},
watch: {
// nightMode的值變動時,切換地圖的日間/夜間樣式
nightMode: function(val) {
if(val == 'open') {
this.map.setOptions({
styles: this.nightModeStyles
});
}
else {
this.map.setOptions({
styles: null
});
}
}
}
});

以下是頁面截圖:

Google Maps night mode demo

2 個重要的連結

這邊附上 2 個重要連結:

  1. 每一個 feature、element 的說明:

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

  1. styles 生成器:

https://mapstyle.withgoogle.com/

有了以上這 2 個連結,想用一些自訂的模式就比較容易了。

隱藏地圖上其他商家

以下附上隱藏商家的 demo,主要是覺得會有企畫提出「地圖上東西太多太干擾了想隱藏掉」、「地圖上出現其他同業了啦要刪掉」這種需求。

其實會了上面的 night mode 以後,隱藏商家就容易了,設定的 styles 比夜間模式還少,styles 上改成以下就行:

hideStyle: [{  
  featureType: 'poi.business',  
  stylers: [{  
    visibility: 'off'  
  }]  
}]
Enter fullscreen mode Exit fullscreen mode

頁面的截圖如下:

Google Maps hide business

拿掉商家後,地圖一整個乾淨很多。


隱藏地圖上 UI

Google Maps 上有一些預設的 UI,如下圖紅框:

Google Maps 預設顯示的 UI

Google Maps API 有提供參數去隱藏/顯示它們,如下:

  • zoomControl: boolean
  • mapTypeControl: boolean
  • scaleControl: boolean
  • streetViewControl: boolean
  • rotateControl: boolean
  • fullscreenControl: boolean

要使用時,只要在建立地圖上加上這些參數就行了,例如:

this.map = new google.maps.Map(document.getElementById('map'), {  
  center: location,  
  zoom: 16,  
  mapTypeId: 'terrain',  
  mapTypeControl: false  
});
Enter fullscreen mode Exit fullscreen mode

本篇原始檔下載

Google Maps API 的官方文件有好幾頁,有很多東西可以玩,這篇是看到現在的第一階段筆記,記錄一下覺得以後會遇到的需求。

上面的原始碼整理在 Github 上了,歡迎使用:

https://github.com/letswritetw/letswrite-google-map-api-1


Google Maps API 學習筆記系列

  1. 地圖、標記、客製樣式
  2. 在地圖上畫個日本結界
  3. 用熱圖(Heat Map)製作全台 12 小時雨量分佈圖
  4. Place API 自動完成地址、地點評論摘要
  5. 抓目前位置、計算到各點距離
  6. 畫新冠肺炎分佈圖

Speedy emails, satisfied customers

Postmark Image

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs