Google Maps APIを使ってマーカーを立ててサーバーレスなAPIで保存する

Google Maps JavaScript API(自分でGoogle Mapを使って色々出来るやつ)を使い、Mapをクリックしてピンを立て、ブラウザリロード後も状態を保持するWebアプリケーションを作ってみたくなったので、Serverlessな感じで作ってみました!
2019.09.09

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

入社時健康診断結果について話があると産業医の先生に呼び出されてビクビクしている、もこ@札幌オフィスです。

…さて、Google Maps JavaScript API(自分でGoogle Mapを使って色々出来るやつ)を使い、Mapをクリックしてピンを立て、ブラウザリロード後も状態を保持するWebアプリケーションを作ってみたくなったので、Serverlessな感じで作ってみました!

構成イメージ

API GatewayとLambdaとDynamoDBを利用するシンプルなサーバーレス構成です。

Google Maps JavaScript APIを使ってピンを立てる方法

Google Maps でMarker(ピン)を立てる場合、google.maps.event.addListenerを利用し、Click Eventを受け取り、Event座標からMarkerを立てることができます。

https://developers.google.com/maps/documentation/javascript/events

function initMap() {
  const map = new google.maps.Map(document.getElementById('map'), {
    zoom: 4,
    center: {lat: 42, lng: 141} // 適当な座標
  });

  google.maps.event.addListener(map, 'click', event => clickListener(event, map));
}


function clickListener(event, map) {
  const lat = event.latLng.lat();
  const lng = event.latLng.lng();
  const marker = new google.maps.Marker({
    position: {lat, lng},
    map
  });
}

簡単ですね。

これでクリックしたところにマーカーが設置されていきます

サーバーのインフラを作る

上記方法で作成したマーカーは勿論保存されることはないので、ページを更新すると消えてしまいます。

クリックしてマーカーを作成したタイミングでAPI Gatewayを叩き、座標を保存、MapをInitするタイミングで座標一覧を取得してMarkerを作成できるようにしてみましょう。 SAMを利用して簡単にデプロイ出来るようにしていきます。

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Google Map SAM Template
Resources:
  locationFunctions:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: server/location
      Handler: app.handler
      Runtime: nodejs10.x
      Policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref DynamoDB
      Events:
        GETLocation:
          Type: Api
          Properties:
            Path: /location
            Method: GET
        PUTLocation: 
          Type: Api
          Properties:
            Path: /location
            Method: POST
  DynamoDB:
    Type: AWS::Serverless::SimpleTable
    Properties:
      TableName: "googlemap"
      PrimaryKey:
        Name: id
        Type: String
      ProvisionedThroughput:
        ReadCapacityUnits: 5
        WriteCapacityUnits: 5

これで/locationが叩かれると server/location/app.js のLambdaが実行されるようになります。

座標の保存、取得のLambdaを作成する

POSTの際は送信される座標をDynamoDBに保存し、GETの場合はDynamoDBから全座標を返却するLambda関数を作成します。

const aws = require('aws-sdk');
const uuid = require('uuid/v4')
const documentClient = new aws.DynamoDB.DocumentClient({region: "ap-northeast-1"})

const TableName = "googlemap";


exports.handler = async (event, context, callback) => {
  // POSTだったら座標をDynamoDBに登録する
  if(event.httpMethod === "POST") {
    const body = JSON.parse(event.body)
    const params = {
      TableName,
      Item:{
        id: uuid(),
        lat: body.lat,
        lng: body.lng
      }
    }
    const result = await documentClient.put(params).promise();

    callback(null,{statusCode: result != null ? 200 : 502, headers: {"Access-Control-Allow-Origin": "*"}})

  }
  // GETだったら座標一覧を返却する
  if (event.httpMethod === "GET") {
    const params = {
      TableName
    }
    const result = await documentClient.scan(params).promise()

    console.log(result.Items)
    callback(null, {statusCode: 200, body: JSON.stringify(result.Items), headers: {"Access-Control-Allow-Origin": "*"}})
  }
}

フロントで座標のGET、POSTを実装する

initMapでAPI GatewayにGETリクエストを発行し、マーカーを作成、clickListenerでマーカーを作成、API GatewayにPOSTします。

async function initMap() {
  const map = new google.maps.Map(document.getElementById('map'), {
    zoom: 4,
    center: {lat: 42, lng: 141}
  });
  const result = await fetch("https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/location");

  for (const data of await result.json()) {
    const marker = new google.maps.Marker({
      position: {lat: data.lat, lng: data.lng},
      map: map
    });
  }
  google.maps.event.addListener(map, 'click', event => clickListener(event, map));
}


function clickListener(event, map) {
  const lat = event.latLng.lat();
  const lng = event.latLng.lng();
  console.log({lat, lng})
  const marker = new google.maps.Marker({
    position: {lat, lng},
    map
  });
  const url = "https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/location"

  const data = {
    lat,
    lng
  }
  fetch(url, {
    method: "POST",
    mode: "cors",
    body: JSON.stringify(data)
  }).then(response => {console.log(response)})
}

デプロイ

一個一個コマンドを実行するのが面倒だったので、一つのシェルにまとめました

sam build &&
sam package --s3-bucket BUCKET_NAME_HERE --output-template-file packaged.yaml  &&
sam deploy --template-file packaged.yaml --stack-name location-stack  --capabilities CAPABILITY_IAM

これでマーカーをサーバーレスな感じで保存出来るようになりました。めでたしめでたし。

※上記コードはGitHubで公開しています。