AWS CDK と AWS CloudFormation で Amazon Location Service のリソースを構築する
Author : 桐本 靖規 (AWS DevTools Hero)
こんにちは、AWS DevTools Hero の桐本です。
本日は、AWS Cloud Development Kit (AWS CDK) と AWS CloudFormation を用いて、AWSの環境下で位置情報アプリケーションを構築可能な、Amazon Location Service の構築方法を紹介します。そして、最近話題の CDK Migrate や AWS CloudFormation IaCジェネレーター の例も紹介します。
AWS CDK と AWS CloudFormation を利用することで、インフラストラクチャをコードとして管理し、これにより開発プロセスを自動化し効率化することが可能です。AWS CDK はプログラミング言語を用いて、開発者に親しみやすく、コンポーネントの再利用を容易にします。AWS CloudFormation は、安定したリソース管理を実現し、JSON や YAML でテンプレートを作成できます。これらのツールを利用することで、コード化され再現可能なインフラストラクチャを構築するというメリットが得られます。
Amazon Location Service については、昨年リリースされた Amazon Location SDK と API キー機能 を利用し、マップ・ジオコーディング・ルーティングの3機能を構築します。また、今年発表された「API キーとリソース管理の CloudFormation サポートを開始」についても、AWS CDK と AWS CloudFormation での実装を検証しました。
下記の流れで例を紹介します。リソースの構築には、AWS CDK か AWS CloudFormation のどちらかを選んでください。
目次
ご注意
本記事で紹介する AWS サービスを起動する際には、料金がかかります。builders.flash メールメンバー特典の、クラウドレシピ向けクレジットコードプレゼントの入手をお勧めします。
このクラウドレシピ (ハンズオン記事) を無料でお試しいただけます »
毎月提供されるクラウドレシピのアップデート情報とともに、クレジットコードを受け取ることができます。
1. 事前準備
AWS CDK の環境をインストールします。今回、AWS CloudFormation を利用する方はインストール不要です。
執筆時の検証バージョン
- node v20.0.0
- npm v9.6.4
パッケージをインストールします。
npm install -g aws-cdk
バージョンを確認します。
cdk --version
2.127.0 (build 6c90efc)
2. Amazon Location Service のリソース構築 : AWS CloudFormation
はじめに、AWS CloudFormation で Amazon Location Service のリソースを構築します。
GitHub で作成した環境を公開しています。ご自身の環境に fork またはダウンロードしご利用ください。
aws-cloudformation-templates-showcase - location-service
create.yml
AWSTemplateFormatVersion: 2010-09-09
Description: Amazon Location Service Creation
Parameters:
MapName:
Description: Map Name
Type: String
PlaceIndexName:
Description: PlaceIndex Name
Type: String
RouteCalculatorName:
Description: RouteCalculator Name
Type: String
APIKeyName:
Description: APIKey Name
Type: String
Resources:
LocationServiceMap:
Type: AWS::Location::Map
Properties:
Configuration:
Style: VectorHereExplore
Description: Amazon Location Service Map
MapName: !Sub ${MapName}
PricingPlan: RequestBasedUsage
LocationServicePlaceIndex:
Type: AWS::Location::PlaceIndex
Properties:
DataSource: Here
DataSourceConfiguration:
IntendedUse: SingleUse
Description: Amazon Location Service PlaceIndex
IndexName: !Sub ${PlaceIndexName}
PricingPlan: RequestBasedUsage
LocationServiceRouteCalculator:
Type: AWS::Location::RouteCalculator
Properties:
DataSource: Here
Description: Amazon Location Service eRouteCalculator
CalculatorName: !Sub ${RouteCalculatorName}
PricingPlan: RequestBasedUsage
LocationServiceAPIKey:
Type: AWS::Location::APIKey
Properties:
Description: Amazon Location Service APIKey
KeyName: !Sub ${APIKeyName}
NoExpiry: true
Restrictions:
AllowActions: [geo:GetMap*, geo:SearchPlaceIndexForPosition, geo:CalculateRoute]
AllowResources:
- !Sub arn:aws:geo:${AWS::Region}:${AWS::AccountId}:map/${MapName}
- !Sub arn:aws:geo:${AWS::Region}:${AWS::AccountId}:place-index/${PlaceIndexName}
- !Sub arn:aws:geo:${AWS::Region}:${AWS::AccountId}:route-calculator/${RouteCalculatorName}
Outputs:
RegionName:
Description: Region Name
Value: !Sub ${AWS::Region}
MapName:
Description: Map Name
Value: !Ref LocationServiceMap
PlaceIndexName:
Description: PlaceIndex Name
Value: !Ref LocationServicePlaceIndex
RouteCalculatorName:
Description: RouteCalculator Name
Value: !Ref LocationServiceRouteCalculator
AWS CloudFormationでデプロイ
作成したテンプレートを使って AWS CloudFormationでAmazon Location Service のリソースをデプロイします。
AWSマネジメントコンソール → AWS CloudFormation → 「スタックの作成」をクリックします。
クリックすると拡大します
「新しいリソースを使用」をクリックします。
クリックすると拡大します
前提条件は「テンプレートの準備完了」を選択します。テンプレートの指定は「テンプレートファイルのアップロード」を選択しファイルをアップロード → 「次へ」をクリックします。CloudFormation テンプレートは「create.yml」を利用します。
クリックすると拡大します
任意のスタック名・APIキー名・マップ名・ジオコーディング名・ルーティング名を設定 → 「次へ」をクリックします。
クリックすると拡大します
スタックオプションは今回デフォルトで設定 → 「次へ」をクリックします。
クリックすると拡大します
設定を確認 → 「送信」をクリックします。
クリックすると拡大します
しばらくすると、スタックが作成されたのを確認できます。
クリックすると拡大します
3. Amazon Location Service のリソース構築 : AWS CDK
次に、AWS CDK で Amazon Location Service のリソースを構築します。
AWS CDKをはじめて利⽤するかたは、ワークショップ もぜひご確認ください。
GitHubで 作成した環境を公開しています。ご自身の環境に fork またはダウンロードしご利用ください。
aws-cdk-templates-showcase - location-service
ファイル構成
.
├── README.md
├── bin
│ └── location-service.ts
├── cdk.json
├── jest.config.js
├── lib
│ └── location-service-stack.ts
├── package-lock.json
├── package.json
├── test
└── tsconfig.json
package.json
{
"name": "location-service",
"version": "0.1.0",
"bin": {
"location-service": "bin/location-service.js"
},
"scripts": {
"build": "tsc",
"watch": "tsc -w",
"test": "jest",
"cdk": "cdk"
},
"keywords": [],
"author": "Yasunori Kirimoto",
"license": "ISC",
"devDependencies": {
"@types/jest": "^29.5.12",
"@types/node": "20.11.16",
"jest": "^29.7.0",
"ts-jest": "^29.1.2",
"aws-cdk": "2.127.0",
"ts-node": "^10.9.2",
"typescript": "~5.3.3"
},
"dependencies": {
"aws-cdk-lib": "2.127.0",
"constructs": "^10.0.0",
"source-map-support": "^0.5.21"
}
}
/lib/location-service-stack.ts
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as location from 'aws-cdk-lib/aws-location';
export interface LocationServiceStackProps extends cdk.StackProps {
readonly mapName: string;
readonly placeIndexName: string;
readonly routeCalculatorName: string;
readonly apiKeyName: string;
}
/**
* Amazon Location Service Creation
*/
export class LocationServiceStack extends cdk.Stack {
public constructor(scope: Construct, id: string, props: LocationServiceStackProps) {
super(scope, id, props);
// Amazon Location Service APIKey
const locationServiceApiKey = new location.CfnAPIKey(this, 'LocationServiceAPIKey', {
description: 'Amazon Location Service APIKey',
keyName: `${props.apiKeyName!}`,
noExpiry: true,
restrictions: {
allowActions: [
'geo:GetMap*',
'geo:SearchPlaceIndexForPosition',
'geo:CalculateRoute',
],
allowResources: [
`arn:aws:geo:${this.region}:${this.account}:map/${props.mapName}`,
`arn:aws:geo:${this.region}:${this.account}:place-index/${props.placeIndexName}`,
`arn:aws:geo:${this.region}:${this.account}:route-calculator/${props.routeCalculatorName}`,
],
},
});
// Amazon Location Service Map
const locationServiceMap = new location.CfnMap(this, 'LocationServiceMap', {
configuration: {
style: 'VectorHereExplore',
},
description: 'Amazon Location Service Map',
mapName: props.mapName,
pricingPlan: 'RequestBasedUsage',
});
// Amazon Location Service Place Index
const locationServicePlaceIndex = new location.CfnPlaceIndex(this, 'LocationServicePlaceIndex', {
dataSource: 'Here',
dataSourceConfiguration: {
intendedUse: 'SingleUse',
},
description: 'Amazon Location Service PlaceIndex',
indexName: props.placeIndexName,
pricingPlan: 'RequestBasedUsage',
});
// Amazon Location Service Route Calculator
const locationServiceRouteCalculator = new location.CfnRouteCalculator(this, 'LocationServiceRouteCalculator', {
dataSource: 'Here',
description: 'Amazon Location Service eRouteCalculator',
calculatorName: props.routeCalculatorName,
pricingPlan: 'RequestBasedUsage',
});
// Outputs
new cdk.CfnOutput(this, 'CfnOutputRegionName', {
description: 'Region Name',
value: this.region,
});
new cdk.CfnOutput(this, 'CfnOutputMapName', {
description: 'Map Name',
value: locationServiceMap.ref,
});
new cdk.CfnOutput(this, 'CfnOutputPlaceIndexName', {
description: 'PlaceIndex Name',
value: locationServicePlaceIndex.ref,
});
new cdk.CfnOutput(this, 'CfnOutputRouteCalculatorName', {
description: 'RouteCalculator Name',
value: locationServiceRouteCalculator.ref,
});
}
}
/bin/location-service.ts
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { LocationServiceStack } from '../lib/location-service-stack';
const app = new cdk.App();
new LocationServiceStack(app, 'location-service', {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
},
apiKeyName: 'LocationServiceApiKey',
mapName: 'LocationServiceMap',
placeIndexName: 'LocationServicePlace',
routeCalculatorName: 'LocationServiceRoute',
});
AWS CDK でデプロイ
作成したプロジェクトを使って AWS CDK で Amazon Location Service のリソースをデプロイします。
ディレクトリを移動します。
cd aws-cdk-templates-showcase/location-service
パッケージをインストールします。
npm install
デプロイ前に初回のみ下記コマンドを実行します。リージョンを変更した時にも実行します。
cdk bootstrap
クリックすると拡大します
プロジェクトをデプロイします。
cdk deploy
クリックすると拡大します
4. Amazon Location Service のリソース確認
AWS CDK または AWS CloudFormation のデプロイが反映されているかを確認します。
AWS マネジメントコンソール → Amazon Location Service → 各リソースを確認します。
クリックすると拡大します
マップ・ジオコーディング・ルーティングの設定が反映されています。次のアプリケーション構築で利用するため、マップ名・ジオコーディング名・ルーティング名をコピーします。
クリックすると拡大します
API キーの設定が反映されています。次のアプリケーション構築で利用するため、リージョン名・API キー値をコピーします。外部公開時は、API キーのリファラー設定も必要となります。
クリックすると拡大します
ここまでで、AWS CDK または AWS CloudFormation で Amazon Location Service の環境構築が完了しました。次に、Amazon Location Service のアプリケーションを構築します。
5. Amazon Location Service のアプリケーション構築
スターターのインストール
既存のスターターを利用し、Amazon Location Service のフロントエンド環境を構築します。このスターターは、マップライブラリをシンプルに利用できる構成になっています。ご自身の環境に fork またはダウンロードしインストールをしてください。
MapLibre GL JS & Amazon Location Service スターター
執筆時の検証バージョン
- node v20.0.0
- npm v9.6.4
ファイル構成
.
├── LICENSE
├── README.md
├── dist
│ ├── assets
│ └── index.html
├── docs
├── img
├── index.html
├── package-lock.json
├── package.json
├── src
│ ├── main.ts
│ ├── style.css
│ └── vite-env.d.ts
├── tsconfig.json
└── vite.config.ts
.env
デプロイした環境のリージョン・API キー・マップ名を env ファイルに設定します。
VITE_REGION = xxxxx
VITE_MAP_API_KEY = v1.public.xxxxx
VITE_MAP_NAME = xxxxx
ディレクトリを移動します。
cd maplibregljs-amazon-location-service-starter
パッケージをインストールします。
npm install
ローカルサーバーで確認します。
npm run dev
マップが表示されます。
Amazon Location SDK のインストール
次に、Amazon Location SDK の必要なライブラリをインストールします。インストールすることで、API の認証や MapLibre GL JS との組み合わせが手軽になります。
client-location
AWS SDK をインストールします。"client-location" は Amazon Location Service を操作できる SDK です。
npm install @aws-sdk/client-location
amazon-location-utilities-auth-helper
"amazon-location-utilities-auth-helper" をインストールします。Amazon Location Service の API キーと Amazon Cognito の認証が手軽になるライブラリです。
npm install @aws/amazon-location-utilities-auth-helper
amazon-location-utilities-datatypes
"amazon-location-utilities-datatypes" をインストールします。Amazon Location Service のレスポンスを GeoJSON 形式に変換してくれるライブラリです。
npm install @aws/amazon-location-utilities-datatypes
"amazon-location-utilities-datatypes" について、MapLibre GL JS と組み合わせると一部利用しにくかったため、先日オプション機能を追加するコントリビュートをしました。
アプリケーションの構築
最後に、実際に Amazon Location Service のマップ・ジオコーディング・ルーティング機能を API キーを用いて構築する方法を紹介します。
package.json
{
"name": "maplibregljs-amazon-location-service-starter",
"version": "4.0.0",
"description": "",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"keywords": [],
"author": "MapLibre User Group Japan",
"license": "ISC",
"devDependencies": {
"typescript": "^5.3.3",
"vite": "^5.1.1"
},
"dependencies": {
"@aws-sdk/client-location": "^3.511.0",
"@aws/amazon-location-utilities-auth-helper": "^1.0.3",
"@aws/amazon-location-utilities-datatypes": "^1.0.5",
"maplibre-gl": "^4.0.0"
}
}
.env
デプロイした環境のリージョン・API キー・マップ名・ジオコーディング名・ルーティング名を env ファイルに設定します。
VITE_REGION = xxxxx
VITE_API_KEY = v1.public.xxxxx
VITE_MAP_NAME = xxxxx
VITE_PLACE_NAME = xxxxx
VITE_ROUTE_NAME = xxxxx
main.ts
import './style.css'
import 'maplibre-gl/dist/maplibre-gl.css';
import maplibregl from 'maplibre-gl';
// Amazon Location SDKを設定
import { LocationClient, SearchPlaceIndexForPositionCommand, CalculateRouteCommand } from "@aws-sdk/client-location";
import { placeToFeatureCollection, routeToFeatureCollection } from '@aws/amazon-location-utilities-datatypes';
import { withAPIKey } from '@aws/amazon-location-utilities-auth-helper';
const region = import.meta.env.VITE_REGION;
const apiKey = import.meta.env.VITE_API_KEY;
const mapName = import.meta.env.VITE_MAP_NAME;
const placeName = import.meta.env.VITE_PLACE_NAME;
const routeName = import.meta.env.VITE_ROUTE_NAME;
async function initialize() {
// APIキーの認証設定
const authHelper = await withAPIKey(apiKey);
const client = new LocationClient({
region: region,
...authHelper.getLocationClientConfig()
});
// 指定位置からリバースジオコーディング
const inputPlace = {
IndexName: placeName,
Position: [139.767, 35.681],
};
const commandPlace = new SearchPlaceIndexForPositionCommand(inputPlace);
const responsePlace = await client.send(commandPlace);
// ジオコーディングのレスポンスをGeoJSONに変換
const featureCollectionPlace = placeToFeatureCollection(responsePlace, {
flattenProperties: true
});
// 指定位置でルート検索
const inputRoute = {
CalculatorName: routeName,
DeparturePosition: [139.7558, 35.6767],
DestinationPosition: [139.8160, 35.6830],
IncludeLegGeometry: true,
};
const commandRoute = new CalculateRouteCommand(inputRoute);
const responseRoute = await client.send(commandRoute);
// ルーティングのレスポンスをGeoJSONに変換
const featureCollectionRoute = routeToFeatureCollection(responseRoute, {
flattenProperties: true
});
// マップを設定
const map = new maplibregl.Map({
container: 'map',
style: `https://maps.geo.${region}.amazonaws.com/maps/v0/maps/${mapName}/style-descriptor?key=${apiKey}`,
center: [139.767, 35.681],
zoom: 11,
});
map.addControl(
new maplibregl.NavigationControl({
visualizePitch: true,
})
);
map.on('load', function () {
// ジオコーディング結果のスタイル設定
map.addSource("search-result", {
type: "geojson",
data: featureCollectionPlace
});
map.addLayer({
'id': "search-result",
'type': 'circle',
'source': 'search-result',
'layout': {},
'paint': {
'circle-color': '#007cbf',
'circle-radius': 10
}
});
// ジオコーディング結果の情報表示
map.on('click', 'search-result', (e) => {
const coordinates = e.lngLat;
const description = e.features![0].properties['Place.Label'];
new maplibregl.Popup()
.setLngLat(coordinates)
.setHTML(description)
.addTo(map);
});
map.on('mouseenter', 'search-result', () => {
map.getCanvas().style.cursor = 'pointer';
});
map.on('mouseleave', 'search-result', () => {
map.getCanvas().style.cursor = '';
});
// ルーティング結果のスタイル設定
map.addSource("route-result", {
type: "geojson",
data: featureCollectionRoute
});
map.addLayer({
'id': "route-result",
'type': 'line',
'source': 'route-result',
'layout': {
'line-join': 'round',
'line-cap': 'round'
},
'paint': {
'line-color': '#FF0000',
'line-width': 10,
'line-opacity': 0.5
}
});
// ルーティング結果の情報表示
map.on('click', 'route-result', (e) => {
const coordinates = e.lngLat;
const description = `${e.features?.[0]?.properties['Distance'] ?? ''}km`;
new maplibregl.Popup()
.setLngLat(coordinates)
.setHTML(description)
.addTo(map);
});
map.on('mouseenter', 'route-result', () => {
map.getCanvas().style.cursor = 'pointer';
});
map.on('mouseleave', 'route-result', () => {
map.getCanvas().style.cursor = '';
});
});
}
initialize();
ローカルサーバーで確認します。
npm run dev
Amazon Location Service のマップ・ジオコーディング・ルーティング機能が表示されます
ここまでで、Amazon Location Service のアプリケーション構築が完了しました。次に、CDK Migrate と IaC ジェネレーターについて紹介します。
6. CDK Migrate の紹介
CDK Migrate を利用することで、AWS CloudFormation のテンプレートを AWS CDK のプロジェクトに自動で変換できます。今回のプロジェクトも CDK Migrate を利用して作成し、ほとんどのコードをそのまま利用できました。ただ、このプロジェクトはシンプルな構成のため、複雑な構成になると課題はでるかもしれません。
cdk migrate コマンドで AWS CloudFormation のテンプレートを AWS CDK のプロジェクトに変換します。
cdk migrate --stack-name location-service --from-path ./create.yml --language typescript
画像をクリックすると拡大します
7. AWS CloudFormation IaCジェネレーターの紹介
AWS CloudFormation IaCジェネレーター を利用することで、AWS CloudFormation テンプレートを作成し既存のリソースをインポートできます。これは、AWS マネジメントコンソールでリソースを作成してテンプレート化する際に便利になります。今回作成した CloudFormation テンプレートは、一部参考になる点がありましたが、基本的には新たに作成する必要がありました。
AWS マネジメントコンソール → AWS CloudFormation → 「IaC ジェネレーター」をクリック → 「新しいスキャンを開始」をクリック → スキャン完了後「テンプレートを作成」をクリックします。
クリックすると拡大します
「新しいテンプレートから開始」を選択 → テンプレート名を設定 → 「次へ」をクリックします。
クリックすると拡大します
スキャンされた中からテンプレート化したいリソースを選択します。今回は Amazon Location Service 関係のリソースを選択 → 「次へ」をクリックします。
クリックすると拡大します
「次へ」をクリックします。
クリックすると拡大します
設定を確認 → 「テンプレートを作成」をクリック。
クリックすると拡大します
指定したリソースのテンプレートが作成されます。
Metadata:
TemplateId: "arn:aws:cloudformation:ap-northeast-1:xxxxx:generatedTemplate/c686423c-b0d2-4d87-bfe3-ea"
Resources:
LocationRouteCalculator00SampleRouting00Myobr:
UpdateReplacePolicy: "Retain"
Type: "AWS::Location::RouteCalculator"
DeletionPolicy: "Retain"
Properties:
CalculatorName: "SampleRouting"
PricingPlan: "RequestBasedUsage"
Description: ""
Tags: []
DataSource: "Here"
LocationMap00SampleMap00QfNM7:
UpdateReplacePolicy: "Retain"
Type: "AWS::Location::Map"
DeletionPolicy: "Retain"
Properties:
PricingPlan: "RequestBasedUsage"
MapName: "SampleMap"
Configuration:
Style: "VectorHereExplore"
CustomLayers: []
Tags: []
LocationPlaceIndex00SampleGeocoding002C7JQ:
UpdateReplacePolicy: "Retain"
Type: "AWS::Location::PlaceIndex"
DeletionPolicy: "Retain"
Properties:
IndexName: "SampleGeocoding"
PricingPlan: "RequestBasedUsage"
Description: ""
DataSourceConfiguration:
IntendedUse: "SingleUse"
Tags: []
DataSource: "Here"
クリックすると拡大します
8. 各リソースの削除
最後に、各リソースの削除方法を紹介します。
AWS CloudFormation でリソース削除
AWS マネジメントコンソール → AWS CloudFormation → location-service を選択 →「削除」をクリックします。
クリックすると拡大します
「削除」をクリックします。
クリックすると拡大します
AWS CDK でリソース削除
cdk destroy コマンドでリソースを削除します。
cdk destroy
クリックすると拡大します
まとめ
Amazon Location Service の API キーとリソース管理の CloudFormation サポートが開始されたことで、環境構築の自動化やテンプレート化の選択肢が広がりました。今後のアップデートも楽しみです !
また、AWS CDK では Amazon Location Service の L2 コンストラクトは、2024 年 3 月現在 アルファ版 で Place Index のみが提供されています。そのため、基本的には L1 コンストラクトでの定義が必要になります。
今回の例が、AWS で位置情報アプリケーションを構築しようと考えている方々の参考になれば幸いです !
非公式ではありますが、Amazon Location Service のアップデート情報を毎月配信しています。
Monthly Amazon Location Service Updates (JPN)
Monthly Amazon Location Service Updates (ENG)
筆者プロフィール
桐本 靖規
Co-Founder and COO of MIERUNE
AWS DevTools Hero | Amplify Japan User Group | MapLibre User Group Japan |
MapLibre Voting Member | OSGeo Charter Member | Owner of dayjournal
2004 年から位置情報分野に携わり、2016 年に MIERUNE を共同創業。独自のカルチャーを持つプロフェッショナルなチーム作りや、プロダクト成功のための組織マネジメントに注力し日々模索中。個人活動では、オープンソースへの貢献や、コミュニティの運営メンバーとしてカンファレンスやワークショップを開催。専門は GIS (Geographic Information System) と FOSS4G (Free and OpenSource Software for Geospatial)。AWS と位置情報技術の組み合わせを日々模索中。
好きな AWS サービス: Amazon Location Service / AWS Amplify
Twitter: @dayjournal_nori
GitHub: @dayjournal
LinkedIn: @YasunoriKirimoto
AWS を無料でお試しいただけます