BFT名古屋 TECH BLOG

日々の業務で得た知識を所属するエンジニアたちがアウトプットしていきます。

【AWS】【API Gateway】【Lambda】 API GatewayとLambdaでDynamoDBのデータを取得する

初めに

こんにちは、株式会社BFT名古屋支店新人エンジニアのないとうです。
今回はAPI GatewayとLambdaを用いて、DynamoDBから情報を取得する方法について紹介したいと思います。

前提条件

・DynamoDBにデータが保存されている
・DynamoDBのデータを取得できるLambda実行ロールが作成されている

システムについて

システム概要

今回使用するDynamoDBのテーブルはパーティションキーを画像ファイル名、ソートキーを日付、それ以外のデータとして緯度、経度、画像サイズ(縦)、画像サイズ(横)、画像の状態が保存されています。
LambdaはPython3.9で作成していきます。
そしてシステムの要件として、
①座標範囲を入力してLambdaに送信する
②座標範囲内のデータをDBから取得する
③近い座標のデータは1つにまとめる
以上の3つがあります。

①座標範囲を入力してLambdaに送信する

①を実装する方法としてgetメソッドを用いることとしました。 getメソッドは単純なもので【URL】?【変数名】=【値】と記載することで?以降の内容を送信することができる通信方式です。
今回は【変数名】を【minlat】,【minlng】,【maxlat】,【maxlng】として【値】を入力することでLambdaに情報を送信します。
Lambdaに送信された情報はPythonの場合、以下のようにevent['queryStringParameters']配列で受け取ります。

minlat = Decimal(event['queryStringParameters']['minlat'])
maxlat = Decimal(event['queryStringParameters']['maxlat'])
minlng = Decimal(event['queryStringParameters']['minlng'])
maxlng = Decimal(event['queryStringParameters']['maxlng'])

getメソッドの場合文字列で送信されるので、数値変換もデータを受け取った時に行っています。

②座標範囲内のデータをDBから取得する

DynamoDBのテーブルからデータを取得する方法としてPythonでは、getitem,scan,queryがあります。
今回は範囲内のデータを全て取得する必要があるので、データの全取得ができるscanを用いることに決定しました。
以下のようにbetweenで緯度(lat)と経度(lng)でデータを絞り込み、取得します。

resp = table.scan(FilterExpression=Key('lat').between(minlat,maxlat) & Key('lng').between(minlng,maxlng) )

③近い座標のデータは1つにまとめる

google mapsでは最大ズームにした際のスケールバーが5mです。
ですので、今回1つにまとめるデータの範囲は直径5mほどの円にしたいと考えています。
日本のある北緯35度近辺で経度が1度ずれた場合に約90kmほど移動するので、
5mの範囲とするために6*10^{-5}度を範囲としました。
範囲内のデータについては以下のようにまとめるようにしています。
1.DBから取得したデータは辞書型として{Key1:[data1],Key2:[data2],・・・}の形としてリストに追加する
2.範囲内だった場合はそれぞれのKeyごとのリストにdataを追加する
3.範囲外だった場合は1と同様にデータをリストに追加する
4.DBからデータが取得できない場合は空のリストを返す

例(data1とdata2が範囲内に入っており、data3が範囲外だった場合)
[
    {
        Key1:[data1,data2],
        Key2:[data1,data2],
         ・
         ・
         ・
        },
    {
        Key1:[data3],
        Key2:[data3],
                         ・
                         ・
                         ・
  
    }
]

このようなデータ構造にするためには次のようプログラムを作る必要があります。

item=resp["Items"]
        if(len(item)>=1):
            result=[]
            data={'filename':[item[0]['filename']],'time':[item[0]['time']],'lat':[str(item[0]['lat'])],'lng':[str(item[0]['lng'])],'status':[item[0]['status']],'w':[str(item[0]['w'])],'h':[str(item[0]['h'])]}
            result.append(data)
            i=1
            j=0
            while i < len(item):
                while j < len(result):
                    if float(result[j]['lat'][0])-0.00003<item[i]['lat']<float(result[j]['lat'][0])+0.00003 and float(result[j]['lng'][0])-0.00003<item[i]['lng']<float(result[j]['lng'][0])+0.00003:
                        result[j]['filename'].append(item[i]['filename'])
                        result[j]['time'].append(item[i]['time'])
                        result[j]['lat'].append(str(item[i]['lat']))
                        result[j]['lng'].append(str(item[i]['lng']))
                        result[j]['status'].append(item[i]['status'])
                        result[j]['w'].append(str(item[i]['w']))
                        result[j]['h'].append(str(item[i]['h']))
                        j=0

                        break
                    j=j+1
                    if j==len(result):
                        data={'filename':[item[i]['filename']],'time':[item[i]['time']],'lat':[str(item[i]['lat'])],'lng':[str(item[i]['lng'])],'status':[item[i]['status']],'w':[str(item[i]['w'])],'h':[str(item[i]['h'])]}
                        result.append(data)
                        j=0
                        break
                i=i+1
        else:
            result=[]

システムの実装

システム構成

今回作成するシステムは次のような手順で動作するようにします。
①座標範囲を入力してAPI Gatewayにアクセスする
API GatewayがLambdaを呼び出す
③Lambdaが座標範囲に応じたデータを取得する
API GatewayがLambdaのデータを出力する
f:id:bftnagoya:20211022112829p:plain

Lambdaの作成

1.【1から作成】を選択し【関数名】を入力、【ランタイム】に「Python3.9」を選択する。
f:id:bftnagoya:20211020095636p:plain 2.Lambdaの実行ロールをアタッチして関数を作成する。
f:id:bftnagoya:20211020095659p:plain 3.プログラム作成
Lambdaのプログラムを以下のように作成する。

import json
import boto3
from boto3.dynamodb.conditions import Key
from decimal import Decimal
TABLE_NAME='CDN_DIS_EXIFdata'
dynamodb = boto3.resource('dynamodb')


def lambda_handler(event, context):
   
    table = dynamodb.Table(TABLE_NAME)

    #①座標範囲を入力してLambdaに送信する 
    minlat = Decimal(event['queryStringParameters']['minlat'])
    maxlat = Decimal(event['queryStringParameters']['maxlat'])
    minlng = Decimal(event['queryStringParameters']['minlng'])
    maxlng = Decimal(event['queryStringParameters']['maxlng'])

    #②座標範囲内のデータをDBから取得する
    resp = table.scan(FilterExpression=Key('lat').between(minlat,maxlat) & Key('lng').between(minlng,maxlng) )
    item=resp["Items"]

    #③近い座標のデータは1つにまとめる  
    if(len(item)>=1):
        result=[]
        data={'filename':[item[0]['filename']],'time':[item[0]['time']],'lat':[str(item[0]['lat'])],'lng':[str(item[0]['lng'])],'status':[item[0]['status']],'w':[str(item[0]['w'])],'h':[str(item[0]['h'])]}
        result.append(data)
        i=1
        j=0
        while i < len(item):
            while j < len(result):
                if float(result[j]['lat'][0])-0.00003<item[i]['lat']<float(result[j]['lat'][0])+0.00003 and float(result[j]['lng'][0])-0.00003<item[i]['lng']<float(result[j]['lng'][0])+0.00003:
                    result[j]['filename'].append(item[i]['filename'])
                    result[j]['time'].append(item[i]['time'])
                    result[j]['lat'].append(str(item[i]['lat']))
                    result[j]['lng'].append(str(item[i]['lng']))
                    result[j]['status'].append(item[i]['status'])
                    result[j]['w'].append(str(item[i]['w']))
                    result[j]['h'].append(str(item[i]['h']))
                    j=0

                    break
                j=j+1
                if j==len(result):
                    data={'filename':[item[i]['filename']],'time':[item[i]['time']],'lat':[str(item[i]['lat'])],'lng':[str(item[i]['lng'])],'status':[item[i]['status']],'w':[str(item[i]['w'])],'h':[str(item[i]['h'])]}
                    result.append(data)
                    j=0
                    break
            i=i+1
    else:
        result=[]
      
        
    return{
        'statusCode': 200,
        'body': json.dumps(result)
    }
        

API Gatewayの作成と設定

1.REST APIの【構築】をクリックしAPIを作成する。
f:id:bftnagoya:20211020095828p:plain
2.getメソッドを作成し以下の設定値を設定し保存する
【統合タイプ】:「Lambda関数」
【Lambdaプロシキ統合を使用】:チェック
【Lambdaリージョン】:「ap-northeast-1」
【Lambda関数名】:「先ほど作成したLambda関数名」
【デフォルトタイムアウトを使用】:チェック
f:id:bftnagoya:20211020095957p:plain 3.【メソッドレスポンス】の【200のレスポンス本文】に「application/json」を追加する
f:id:bftnagoya:20211020100022p:plain 4.APIをデプロイする

機能の確認

ブラウザで以下のように入力しデータが出力されることが確認されれば完了
APIのURL】?minlat=【数値】,minlng=【数値】,maxlat=【数値】,maxlng=【数値】

終わりに

今回はDynamo DBから情報を取得する方法について紹介しました。
scanを用いてデータを選別しましたが、他にも方法はあると思うので調べてみたいと思います。
LambdaとAPI Gatewayの作成について詳しくはこちら→【AWS】【API Gateway】【Lambda】API GatewayとLambdaでS3の画像を表示する - BFT名古屋 TECH BLOG それでは。

おまけ

GPSの誤差について
GPSの誤差は10mほどだといわれています。
しかし、SONYのホームページを見てみると
「受信状態が良好の状態で約2m程度の誤差が、受信状態が悪い状態では数百mの誤差が発生する場合があります。」
と記載があり、かなり誤差が発生する可能性があるのでGPSの座標を使う場合は注意が必要です。


参照

dev.classmethod.jp

qiita.com

knowledge.support.sony.jp