Google Map API

Posted by Elizabeth Huang on Mon, May 27, 2024

API Key

Maps JavaScript API 會用到

可以限制Key的

  • 使用的API 權限,如Geocoding API
  • 平台使用,如限制IP、網站、Android APP、iOS APP

簽章私鑰

Map Static API 會用到

如果是一次性需求,可以使用官方的小工具;如果需要動態產生簽名,需要從server side 簽名,官方有提供常見語言的範例

官方強烈建議加上簽章,如果每日要求超過 25,000 個,就必須提供 API 金鑰和數位簽章

Maps JavaScript API

document

載入Maps JavaScript API

1<script>
2  (g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})({
3    key: "YOUR_API_KEY",
4    v: "weekly",
5    // Use the 'v' parameter to indicate the version to use (weekly, beta, alpha, etc.).
6    // Add other bootstrap parameters as needed, using camel case.
7  });
8</script>

載入地圖

1const { Map } = await google.maps.importLibrary("maps");
2const map = new Map(document.getElementById("map"), {
3  zoom: 18, // 地圖縮放等級
4  center: { lat: 43.773145, lng: 11.2559602 }, // 地圖中心
5});

可以設置地圖類型mapTypeId

  • 道路地圖(預設)
  • 衛星影像
  • 道路+衛星 混合
  • 地形

標記marker

example

1const marker = new google.maps.Marker({
2  position: { lat: 43.7731426, lng: 11.2456317 }, // 標記的位置
3  map,
4  title: "Hello World!", // 游標懸停時顯示的文字
5});

資訊視窗InfoWindow

example

1const infowindow = new google.maps.InfoWindow({
2  content: '<div><h1>聖母百花大教堂</h1>位於義大利<a href="https://zh.wikipedia.org/wiki/佛羅倫斯">佛羅倫斯</a>的一座天主教堂</div>',
3});
4infowindow.open({anchor: marker, map}); // 配合marker顯示在地圖上

事件event

地圖可以監聽事件,如點擊、游標移動等

example

範例:監聽點擊事件並取得經緯度

1map.addListener("click", (mapsMouseEvent) => {
2	console.log(mapsMouseEvent.latLng.toJSON());
3});

上面的latLng.toJSON()印出來長這樣:

1{
2    "lat": 23.373429728008887,
3    "lng": 6.3121497631073
4}

Geocoding API

document

反向地理編碼

example

使用 經緯度 取得地理資訊

取得的地理資訊會是一個array,從最相符到最不相符排序

範例:監聽點擊事件並取得經緯度,再用經緯度取得地理資訊

 1map.addListener("click", (mapsMouseEvent) => {
 2  geocoder
 3  .geocode({ location: mapsMouseEvent.latLng })
 4  .then((response) => {
 5    if (response.results[0]) {
 6      console.log(response.results[0]);
 7    } else {
 8      window.alert("No results found");
 9    }
10  })
11  .catch((e) => window.alert("Geocoder failed due to: " + e));

上面的response.results[0]印出來長這樣:

 1    {
 2        "address_components": [
 3            {
 4                "long_name": "臺北車站(忠孝)[雙層巴士]",
 5                "short_name": "臺北車站(忠孝)[雙層巴士]",
 6                "types": [
 7                    "establishment",
 8                    "point_of_interest",
 9                    "transit_station"
10                ]
11            },
12            {
13                "long_name": "中正區",
14                "short_name": "中正區",
15                "types": [
16                    "administrative_area_level_2",
17                    "political"
18                ]
19            },
20            {
21                "long_name": "台北市",
22                "short_name": "台北市",
23                "types": [
24                    "administrative_area_level_1",
25                    "political"
26                ]
27            },
28            {
29                "long_name": "台灣",
30                "short_name": "TW",
31                "types": [
32                    "country",
33                    "political"
34                ]
35            },
36            {
37                "long_name": "100",
38                "short_name": "100",
39                "types": [
40                    "postal_code"
41                ]
42            }
43        ],
44        "formatted_address": "100台灣台北市中正區臺北車站(忠孝)[雙層巴士]",
45        "geometry": {
46            "location": {
47                "lat": 25.04644,
48                "lng": 121.517189
49            },
50            "location_type": "GEOMETRIC_CENTER",
51            "viewport": {
52                "south": 25.0450910197085,
53                "west": 121.5158400197085,
54                "north": 25.0477889802915,
55                "east": 121.5185379802915
56            }
57        },
58        "place_id": "ChIJkYopX3KpQjQRFWVH5PyqOSM",
59        "plus_code": {
60            "compound_code": "2GW8+HV 台灣台北市中正區黎明里",
61            "global_code": "7QQ32GW8+HV"
62        },
63        "types": [
64            "establishment",
65            "point_of_interest",
66            "transit_station"
67        ]
68    }
  • place_id:地點專屬ID,如商家、地標等都會有place_id,並且可能隨時間改變。參考官方文件
  • formatted_address:人類可讀地址
  • geometry:經緯度、可視區域等

Map Static API

document

地圖靜態圖片連結

1https://maps.googleapis.com/maps/api/staticmap?parameters

常用parameters:

  • key:API key,必填
  • signature:數位簽章
  • center:圖片中心,格式為緯度,精度
  • zoom:縮放等級
  • size:圖片尺寸
  • format:圖片格式
  • scale:影響回傳圖片的像素等級,可用值為1或2
  • markers:一或多組mark

Map URLs

document

取得某座標和地址的連結

總共有四種格式

  • 搜尋:顯示特定地點圖釘的地圖
  • 路線
  • 顯示地圖:不含標記或路線的地圖
  • 街景服務

搜尋

1https://www.google.com/maps/search/?api=1&parameters

parameters:

  • query:可以為地點名稱、地址或經緯度
  • query_place_id:如果只有經緯度沒有place id,則不會在側邊面板顯示地點資訊(如商家、地標等)

費用

document

每月提供 $200 美元的抵免額 一項產品可能會有多個費率,不同的 SKU(如Maps JavaScript API 的地圖和街景費用不同) SKU 是按用量分級計費,分為三個級別:

  • 0–100,000 次
  • 100,001–500,000 次
  • 500,001 次以上

費用計算方式:SKU 用量 x 每次使用單價

0–100,000 次100,001–500,000 個500,000 個以上
每個 0.005 美元(每 1,000 個 5.00 美元)每個 0.004 美元(每 1,000 個 4.00 美元)請https://cloud.google.com/contact-maps?hl=zh-tw洽詢高用量定價資訊
0–100,000 次100,001–500,000 個500,000 個以上
每次 0.002 美元(每 1,000 次 2.00 美元)每次 0.0016 美元(每 1,000 次 1.60 美元)請https://cloud.google.com/contact-maps?hl=zh-tw洽詢高用量定價資訊

Static Maps 計費

0–100,000 次100,001–500,000 次500,000 次以上
每次 $0.007 美元(每 1,000 次 $7.00 美元)每次 $0.0056 美元(每 1,000 次 $5.60 美元)請https://cloud.google.com/contact-maps?hl=zh-tw洽詢高用量定價資訊
  • Maps URLs(取得某座標和地址的連結):不計費

費用計算器: https://mapsplatform.google.com/pricing/?hl=zh-tw

範例

顯示地圖,點擊地圖上的一點後,console顯示三個連結,分別是:

  • 靜態地圖連結
  • 地址連結
  • 地址連結,含 place_id,會找點擊的地點最近的有place_id的點,因此可能不準確

index.html

 1<html>
 2  <head>
 3    <title>Event Click LatLng</title>
 4    <script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script>
 5
 6    <link rel="stylesheet" type="text/css" href="./style.css" />
 7    <script type="module" src="./index.js"></script>
 8  </head>
 9  <body>
10    <div id="map"></div>
11
12    <!-- prettier-ignore -->
13    <script>(g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})
14        ({key: "YOUR_API_KEY", v: "weekly"});</script>
15  </body>
16</html>

style.css

1#map {
2    height: 100%;
3}
4html,
5body {
6    height: 100%;
7    margin: 0;
8    padding: 0;
9}

index.js

 1var APIKey = 'YOUR_API_KEY';
 2var marker;
 3
 4async function initMap() {
 5  // Request needed libraries.
 6  const { Map } = await google.maps.importLibrary("maps");
 7  const geocoder = new google.maps.Geocoder();
 8  const map = new Map(document.getElementById("map"), {
 9    zoom: 18,
10    center: { lat: 43.773145, lng: 11.2559602 },
11  });
12  // Create the initial InfoWindow.
13  let infoWindow = new google.maps.InfoWindow();
14
15  // Configure the click listener.
16  map.addListener("click", (mapsMouseEvent) => {
17    console.log(mapsMouseEvent.latLng.toJSON());
18    geocoder
19    .geocode({ location: mapsMouseEvent.latLng })
20    .then((response) => {
21      if (response.results[0]) {
22        console.log(response.results[0]);
23        // map.setZoom(11);
24
25        if (marker) {
26          marker.setPosition(mapsMouseEvent.latLng);
27        } else {
28          marker = new google.maps.Marker({
29            position: mapsMouseEvent.latLng,
30            map: map,
31          });
32        }
33
34        infoWindow.setContent(response.results[0].formatted_address);
35        infoWindow.open(map, marker);
36
37        const center = `${mapsMouseEvent.latLng.lat()},${mapsMouseEvent.latLng.lng()}`;
38        const picture = `http://maps.googleapis.com/maps/api/staticmap?center=${center}&zoom=${map.getZoom()}&size=800x600&markers=${center}&scale=2&format=jpeg&key=${APIKey}`;
39
40        console.log(picture);
41
42        var url = `https://www.google.com/maps/search/?api=1&query=${center}`;
43        console.log(url);
44        if (response.results[0].place_id) {
45          url += `&query_place_id=${response.results[0].place_id}`;
46          console.log(url);
47        }
48      } else {
49        window.alert("No results found");
50      }
51    })
52    .catch((e) => window.alert("Geocoder failed due to: " + e));
53  });
54}
55
56initMap();