DEV Community

Cover image for Building an Amazon Location Service Resources with AWS CDK and AWS CloudFormation
Yasunori Kirimoto for AWS Heroes

Posted on

Building an Amazon Location Service Resources with AWS CDK and AWS CloudFormation

Today, I will show you how to build Amazon Location Service, which allows you to build location-based applications within your AWS environment using AWS Cloud Development Kit (AWS CDK) and AWS CloudFormation. I will also show examples of the recently popular CDK Migrate and AWS CloudFormation IaC generator.

AWS CDK and AWS CloudFormation allow you to code and manage your infrastructure and automate and streamline the development process. AWS CDK uses a developer-friendly programming language to facilitate component reuse. AWS CloudFormation, on the other hand, offers stable resource management and the ability to create templates using JSON and YAML. You can enjoy the benefits of building a coded and reproducible infrastructure by leveraging these tools.

For Amazon Location Service, I will use the Amazon Location SDK and API key functionality released last year to build three functions: mapping, geocoding, and routing. Also, for the "Launch of CloudFormation Support for API Keys and Resource Management」" announced this year, I verified the implementation with AWS CDK and AWS CloudFormation.

img

An example is presented in the following flow. You can choose either AWS CDK or AWS CloudFormation to build your resources.

  1. Advance Preparation
  2. Building an Amazon Location Service Resource: AWS CloudFormation
  3. Building an Amazon Location Service Resource: AWS CDK
  4. Checking an Amazon Location Service Resource
  5. Building an Amazon Location Service Application
  6. Introduction to CDK Migrate
  7. Introduction to AWS CloudFormation IaC Generator
  8. Summary

Advance Preparation

Install the AWS CDK environment. Installation is not required if you are using AWS CloudFormation.

Verified version at the time of writing

  • node v20.0.0
  • npm v9.6.4

Install the package

npm install -g aws-cdk
Enter fullscreen mode Exit fullscreen mode

Check the version

cdk --version
Enter fullscreen mode Exit fullscreen mode
2.127.0 (build 6c90efc)
Enter fullscreen mode Exit fullscreen mode

Building an Amazon Location Service Resource: AWS CloudFormation

First, build Amazon Location Service resources with AWS CloudFormation.

The environment we created is available on GitHub. Please fork or download and use it in your environment.
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
Enter fullscreen mode Exit fullscreen mode

Deploy with AWS CloudFormation

Deploy the Amazon Location Service resource with AWS CloudFormation using the created template.

AWS Management Console → AWS CloudFormation → "Create Stack".
img

Click on "With New Resources."
img

For the prerequisite, select "Template is ready." To specify a template, select "Upload a template file" and upload the file → Click "Next".
img

Set an arbitrary stack name, API key name, map name, geocoding name, and routing name → Click "Next".
img

Set the stack options as default this time → Click "Next."
img

Confirm settings → Click "Submit".
img

After a few moments, you will see that the stack has been created.
img

Building an Amazon Location Service Resource: AWS CDK

Next, build Amazon Location Service resources with AWS CDK.

The environment we created is available on GitHub. Please fork or download and use it in your environment.
aws-cdk-templates-showcase - location-service

File Configuration

.
├── README.md
├── bin
│   └── location-service.ts
├── cdk.json
├── jest.config.js
├── lib
│   └── location-service-stack.ts
├── package-lock.json
├── package.json
├── test
└── tsconfig.json
Enter fullscreen mode Exit fullscreen mode

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

/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,
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

/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',
});
Enter fullscreen mode Exit fullscreen mode

Deployment with AWS CDK

Deploy Amazon Location Service resources with AWS CDK using the project you have created.

Move the directory

cd aws-cdk-templates-showcase/location-service
Enter fullscreen mode Exit fullscreen mode

Install the package

npm install
Enter fullscreen mode Exit fullscreen mode

Run the following command only the first time before deploying. Run it again when you change the region.

cdk bootstrap
Enter fullscreen mode Exit fullscreen mode

img

Deploy the project

cdk deploy
Enter fullscreen mode Exit fullscreen mode

img

Checking an Amazon Location Service Resource

Check if the AWS CDK or AWS CloudFormation deployment is reflected.

AWS Management Console → Amazon Location Service → each resource.
img

Map, geocoding, and routing settings are reflected. Copy the map name, geocoding name, and routing name for use in the next application build.
img

The API key settings are reflected. Copy the region name and API key value for the next application build. When publishing externally, the API key referrer setting is also required.
img

At this point, you have completed building the Amazon Location Service environment with AWS CDK or AWS CloudFormation. Next, we will build the Amazon Location Service application.

Building an Amazon Location Service Application

Installing the Starter

Use an existing starter to build the Amazon Location Service front-end environment. This starter is configured to make simple use of the map library. You can fork, download, and install it in your environment.

MapLibre GL JS and Amazon Location Service Starter

maplibregljs-amazon-location-service-starter

img

Validated version at the time of writing

  • node v20.0.0
  • npm v9.6.4

File Configuration

.
├── 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
Enter fullscreen mode Exit fullscreen mode

.env

Set the region, API key, and map name of your deployed environment in the env file.

VITE_REGION = xxxxx
VITE_MAP_API_KEY = v1.public.xxxxx
VITE_MAP_NAME = xxxxx
Enter fullscreen mode Exit fullscreen mode

Move the directory

cd maplibregljs-amazon-location-service-starter
Enter fullscreen mode Exit fullscreen mode

Install the package

npm install
Enter fullscreen mode Exit fullscreen mode

Start local server

npm run dev
Enter fullscreen mode Exit fullscreen mode

img

Install Amazon Location SDK

Next, install the necessary libraries for the Amazon Location SDK. The installation will make it easier to authenticate the API and combine it with MapLibre GL JS.

client-location

Install the AWS SDK. "client-location" is an SDK that allows you to manipulate the Amazon Location Service.

npm install @aws-sdk/client-location
Enter fullscreen mode Exit fullscreen mode

amazon-location-utilities-auth-helper

Install "amazon-location-utilities-auth-helper," a library that facilitates authentication with Amazon Location Service API keys and Cognito.

npm install @aws/amazon-location-utilities-auth-helper
Enter fullscreen mode Exit fullscreen mode

amazon-location-utilities-datatypes

Install "amazon-location-utilities-datatypes," a library that converts Amazon Location Service responses to GeoJSON format.

npm install @aws/amazon-location-utilities-datatypes
Enter fullscreen mode Exit fullscreen mode

I contributed to "amazon-location-utilities-datatypes" to add an optional feature because it was sometimes difficult to use in combination with MapLibre GL JS!

AWS Geospatial

Building an Application

Finally, I will show how to build the mapping, geocoding, and routing functionality of the Amazon Location Service using API keys.

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

.env

Set the region, API key, map name, geocoding name, and routing name of the deployed environment in an env file.

VITE_REGION = xxxxx
VITE_API_KEY = v1.public.xxxxx
VITE_MAP_NAME = xxxxx
VITE_PLACE_NAME = xxxxx
VITE_ROUTE_NAME = xxxxx
Enter fullscreen mode Exit fullscreen mode

main.ts

import './style.css'
import 'maplibre-gl/dist/maplibre-gl.css';
import maplibregl from 'maplibre-gl';
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() {
    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);
    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);
    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();
Enter fullscreen mode Exit fullscreen mode

Start local server

npm run dev
Enter fullscreen mode Exit fullscreen mode

You will see the map, geocoding, and routing functions of Amazon Location Service.
img

At this point, we have completed building the Amazon Location Service application. Next, I will introduce CDK Migrate and IaC Generator.

Introduction to CDK Migrate

CDK Migrate can be used to automatically convert AWS CloudFormation templates into AWS CDK projects. This project was also created using CDK Migrate, and most of the code was available. However, this project is a simple configuration, so there may be issues with more complex configurations.

The CDK migrate command converts the AWS CloudFormation template into an AWS CDK project.

cdk migrate --stack-name location-service --from-path ./create.yml --language typescript
Enter fullscreen mode Exit fullscreen mode

img

Introduction to AWS CloudFormation IaC Generator

The AWS CloudFormation IaC generator allows you to create AWS CloudFormation templates and import existing resources. This comes in handy when creating and templating resources in the AWS Management Console. The CloudFormation template we created this time was helpful in some respects but had to be created anew.

AWS Management Console → AWS CloudFormation → Click "IaC Generator" → Click "Start a new scan" → After the scan is complete, click "Create template".
img

Select "Start from a new template" → Set a template name → Click "Next".
img

Select the resource you want to make into a template from the scanned resources. In this case, select a resource related to Amazon Location Service → Click "Next".
img

Click "Next.
img

Confirm the settings → Click "Create template".
img

A template for the specified resource will be created.
img

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"
Enter fullscreen mode Exit fullscreen mode

Summary

CloudFormation support for API keys and resource management in Amazon Location Service is now available, giving you more options for automating and templating your environment builds. I look forward to future updates!

Also, the AWS CDK only provides the Place Index for Amazon Location Service L2 constructs in alpha as of March 2024. So, you will need to define it in the L1 construct.

I hope this example will be helpful to those building location-based applications on AWS!

Unofficially, I distribute monthly updates of Amazon Location Service.
Monthly Amazon Location Service Updates (JPN)
Monthly Amazon Location Service Updates (ENG)

Related Articles

References
Amazon Location Service
AWS Cloud Development Kit (AWS CDK)
AWS CloudFormation
MapLibre

Top comments (1)

Collapse
 
rdarrylr profile image
Darryl Ruggles

Cool example - thanks for sharing!