amazon-location-service
Integrates Amazon Location Service APIs for AWS applications. Use this skill when users want to add maps (interactive MapLibre or static images); geocode addresses to coordinates or reverse geocode coordinates to addresses; calculate routes, travel times, or service areas; find places and businesses through text search, nearby search, or autocomplete suggestions; retrieve detailed place information including hours, contacts, and addresses; monitor geographical boundaries with geofences; or track device locations. Covers authentication, SDK integration, and all Amazon Location Service capabilities.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install awslabs-agent-plugins-amazon-location-service
Repository
Skill path: plugins/amazon-location-service/skills/amazon-location-service
Integrates Amazon Location Service APIs for AWS applications. Use this skill when users want to add maps (interactive MapLibre or static images); geocode addresses to coordinates or reverse geocode coordinates to addresses; calculate routes, travel times, or service areas; find places and businesses through text search, nearby search, or autocomplete suggestions; retrieve detailed place information including hours, contacts, and addresses; monitor geographical boundaries with geofences; or track device locations. Covers authentication, SDK integration, and all Amazon Location Service capabilities.
Open repositoryBest for
Primary workflow: Ship Full Stack.
Technical facets: Full Stack, Integration.
Target audience: everyone.
License: MIT-0.
Original source
Catalog source: SkillHub Club.
Repository owner: awslabs.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install amazon-location-service into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/awslabs/agent-plugins before adding amazon-location-service to shared team environments
- Use amazon-location-service for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: amazon-location-service
description: Integrates Amazon Location Service APIs for AWS applications. Use this skill when users want to add maps (interactive MapLibre or static images); geocode addresses to coordinates or reverse geocode coordinates to addresses; calculate routes, travel times, or service areas; find places and businesses through text search, nearby search, or autocomplete suggestions; retrieve detailed place information including hours, contacts, and addresses; monitor geographical boundaries with geofences; or track device locations. Covers authentication, SDK integration, and all Amazon Location Service capabilities.
license: MIT-0
metadata:
author: aws-geospatial
version: "1.0"
---
## Overview
Amazon Location Service provides geospatial APIs for maps, geocoding, routing, places search, geofencing, and tracking. Prefer the bundled JavaScript client (@aws/amazon-location-client) for web development and use resourceless API operations to avoid managing AWS resources.
## When to Use This Skill
Use this skill when:
- Building location-aware web or mobile applications
- Working with Amazon Location Service projects
- Implementing maps, geocoding, routing, or places search
- Adding geofencing or device tracking functionality
- Integrating geospatial features into AWS applications
Do NOT use this skill for:
- Google Maps, Mapbox, or Leaflet-with-OSM projects (unless migrating to Amazon Location)
- Generic GIS operations without AWS context
- Non-AWS geospatial services
## Amazon Location Service API Overview
**Places** (SDK: geo-places, JS: @aws-sdk/client-geo-places)
- Geocode (Forward/Reverse): Convert addresses to coordinates and vice versa
- Search (Text/Nearby): Find points of interest with contact and hours info
- Autocomplete: Predict addresses based on user input
- Suggest: Predict places and points of interest based on partial or misspelled user input
- Get Place: Retrieve place details by place ID
**Maps** (SDK: geo-maps, JS: @aws-sdk/client-geo-maps)
- Dynamic Maps: Interactive maps using tiles with [MapLibre](https://maplibre.org/) rendering
- Static Maps: Pre-rendered, non-interactive map images, good for including an image into a web page, or for thumbnail images
**Routes** (SDK: geo-routes, JS: @aws-sdk/client-geo-routes)
- Route calculation with traffic and distance estimation
- Service area/isoline creation
- Matrix calculations for multiple origins/destinations
- GPS trace alignment to road segments
- Route optimization (traveling salesman problem)
**Geofences & Trackers** (SDK: location, JS: @aws-sdk/client-location)
- Geofences: Detect entry/exit from geographical boundaries
- Trackers: Current and historical device location tracking
**API Keys** (SDK: location, JS: @aws-sdk/client-location)
- API Keys: Grant access to public applications without exposing AWS credentials
## Common Mistakes
Avoid these frequent errors:
1. **Using `Title` instead of `Address.Label` for display**: In Autocomplete results, always display `Address.Label`. The `Title` field may show components in reverse order and is not suitable for user-facing text.
2. **Using GetStyleDescriptor API for map initialization**: MUST use direct URL passing to MapLibre (`https://maps.geo.{region}.amazonaws.com/v2/styles/Standard/descriptor?key={apiKey}`) instead of making GetStyleDescriptor API calls. The direct URL method is required for proper map rendering.
3. **Forgetting `validateStyle: false` in MapLibre config**: Always set `validateStyle: false` in the MapLibre Map constructor for faster map load times with Amazon Location styles.
4. **Mixing resource-based and resourceless operations**: When possible, prefer resourceless operations (direct API calls without pre-created resources) for simpler deployment and permissions.
5. **Inconsistent API operation naming**: Use the format `service:Operation` when referencing APIs (e.g., `geo-places:Geocode`, `geo-maps:GetStyleDescriptor`). SDK clients use `@aws-sdk/client-*` format.
6. **Not handling nested Address objects correctly**: The Address object from GetPlace contains nested objects (`Region.Code`, `Region.Name`, `Country.Code2`, etc.), not flat strings. Access nested properties correctly.
7. **Wrong action names in API Key permissions**: API key `AllowActions` use `geo-maps:`, `geo-places:`, `geo-routes:` prefixes (e.g., `geo-places:Geocode`, `geo-routes:CalculateRoutes`). Do NOT use SDK client names (`@aws-sdk/client-geo-places`) or IAM-style actions. See the Authentication and Permissions section for the complete list.
## Defaults
Use these default choices unless the user explicitly requests otherwise:
- **JavaScript SDK**: Bundled client (CDN) for browser-only apps; npm modular SDKs (@aws-sdk/client-geo-\*) for React and build tool apps
- **API operations**: Resourceless for Maps/Places/Routes (Geofencing/Tracking always require pre-created resources)
- **Authentication**: API Key for Maps/Places/Routes; Cognito for Geofencing/Tracking
- **Map style**: Standard
- **Coordinate format**: [longitude, latitude] (GeoJSON order)
Override: User can specify "use Cognito for Maps/Places/Routes" or "use bundled client for React".
## API Selection Guidance
Choose the right API for your use case:
### Address Input & Validation
- **Autocomplete** → Type-ahead in address forms (partial input: "123 Main")
- **GetPlace** → Get full details after user selects autocomplete result (by PlaceId)
- **Geocode** → Validate complete user-typed address or convert address to coordinates
### Finding Locations
- **SearchText** → General text search ("pizza near Seattle")
- **SearchNearby** → Find places near a coordinate (restaurants within 5km)
- **Suggest** → Predict places/POIs from partial or misspelled input
- **Autocomplete** → Address-specific predictions (not for general POI search)
### Geocoding
- **Geocode (Forward)** → Address string → Coordinates
- **ReverseGeocode** → Coordinates → Address
### Maps
- **Dynamic Maps (tiles + MapLibre)** → Interactive maps requiring pan, zoom, markers
- **Static Maps (image)** → Non-interactive map images for thumbnails or email
### Routing
- **CalculateRoutes** → Single route between origin and destination
- **CalculateRouteMatrix** → Multiple origins/destinations travel times
- **CalculateIsolines** → Service areas (all locations reachable within time/distance)
## LLM Context Files
When you need detailed API parameter specifications or service capabilities not covered in the reference files, fetch these llms.txt resources:
- **Developer Guide**: https://docs.aws.amazon.com/location/latest/developerguide/llms.txt
- **API Reference**: https://docs.aws.amazon.com/location/latest/APIReference/llms.txt
## Key Guidance for Better Recommendations
### Prefer the Bundled JavaScript Client for Web Development
For convenient web application development, Amazon Location Service provides a bundled JavaScript client that simplifies integration and provides optimized functionality without custom bundling. This bundled client includes all libraries required to build client side web applications with Amazon Location Service.
**Features included in the bundled client:**
- Enables direct pre-bundled dependency inclusion without custom bundle / build
- Simplified authentication and API integration
- TypeScript support with comprehensive type definitions
- Support for all Amazon Location SDKs
**Included SDKs and Libraries:**
- @aws-sdk/client-geo-maps
- @aws-sdk/client-geo-places
- @aws-sdk/client-geo-routes
- @aws-sdk/client-location
- @aws-sdk/credential-providers
- https://github.com/aws-geospatial/amazon-location-utilities-auth-helper-js
**Resources:**
- NPM Package: [@aws/amazon-location-client](https://www.npmjs.com/package/@aws/amazon-location-client)
- GitHub Repository: [aws-geospatial/amazon-location-client-js](https://github.com/aws-geospatial/amazon-location-client-js)
### Prefer Resourceless Operations
Amazon Location Places, Maps and Routes services offer both resource-based and resourceless API operations. Resourceless operations are often simpler and more appropriate for many use cases.
**Resource-based operations** require you to:
- Create and configure Amazon Location Service resources (maps, place indexes, route calculators)
- Manage resource lifecycle and permissions
- Handle resource naming and organization
**Resourceless operations** allow you to:
- Make API calls directly without pre-creating resources
- Reduce deployment complexity
- Simplify IAM permissions and API Key permissions
### Authentication and Permissions
When discussing permissions for Amazon Location Places, Maps and Routes services, always include both IAM permissions and API Key permissions in your guidance. If the type of application being developed is clear, recommend the appropriate authorization tool as described below:
**IAM Permissions** - Recommended for server-side applications and AWS SDK usage:
- Used with AWS credentials (access keys, roles, etc.)
- Provide fine-grained access control
- Required for resource management operations
**API Key Permissions** - Alternative authentication method, especially useful for client-side applications or applications deployed to unauthenticated (public) users:
- Simplified authentication without exposing AWS credentials
- Can be configured with specific allowed operations
- Useful for web and mobile applications
- Supports both resource-based and resourceless operations
- Enables faster subsequent map loads through CDN caching
**API Key Action Names** - API keys use their own action naming convention. Do NOT use SDK client names or IAM action names — they will be rejected.
Resourceless API key actions (recommended):
| Service | AllowActions | AllowResources |
| ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
| Maps | `geo-maps:GetTile`, `geo-maps:GetStaticMap` | `arn:aws:geo-maps:REGION::provider/default` |
| Places | `geo-places:Autocomplete`, `geo-places:Geocode`, `geo-places:ReverseGeocode`, `geo-places:SearchText`, `geo-places:SearchNearby`, `geo-places:Suggest`, `geo-places:GetPlace` | `arn:aws:geo-places:REGION::provider/default` |
| Routes | `geo-routes:CalculateRoutes`, `geo-routes:CalculateRouteMatrix`, `geo-routes:CalculateIsolines`, `geo-routes:OptimizeWaypoints`, `geo-routes:SnapToRoads` | `arn:aws:geo-routes:REGION::provider/default` |
Do NOT use legacy `geo:` prefixed actions (e.g., `geo:GetMap*`, `geo:CalculateRoute`) — these are for pre-created resources only and will not work with resourceless APIs.
## MCP Server Integration
Integrates with the [AWS MCP Server](https://docs.aws.amazon.com/aws-mcp/latest/userguide/what-is-aws-mcp-server.html) (Apache-2.0 license) which provides access to AWS documentation, API references, and direct API interactions. See the [Getting Started Guide](https://docs.aws.amazon.com/aws-mcp/latest/userguide/getting-started-aws-mcp-server.html) for setup and credential configuration. To use a non-default region, add `"--metadata", "AWS_REGION=<your-region>"` to your MCP config args.
## Additional Resources
- [Amazon Location Service Developer Guide](https://docs.aws.amazon.com/location/latest/developerguide/)
- [Amazon Location Service API Reference](https://docs.aws.amazon.com/location/latest/APIReference/)
- [Amazon Location Service Samples](https://github.com/aws-geospatial)
## Reference Files
Load these resources as needed for specific implementation guidance:
- [Address Input](./references/address-input.md) - Create effective address input forms for users with address type ahead completion improving input speed and accuracy
- [Address Verification](./references/address-verification.md) - Validate addresses input from users before taking actions or persisting to databases
- [Calculate Routes](./references/calculate-routes.md) - Calculate routes between locations with customizable travel options and display them on maps
- [Dynamic Map Rendering](./references/dynamic-map.md) - Render dynamic maps with MapLibre
- [Places Search](./references/places-search.md) - Search for places or points of interest
- [Web JavaScript](./references/web-javascript.md) - Integrate Amazon Location services into web browser applications
---
## Referenced Files
> The following files are referenced in this skill and included for context.
### references/address-input.md
```markdown
# Address Input
> **Audience Note**: Keywords MUST, SHOULD, MAY in this document indicate requirements for agent recommendations to users, following RFC 2119.
Create effective address input forms with type-ahead completion that improves input speed and accuracy using Amazon Location Service Places APIs.
## Table of Contents
- [Overview](#overview)
- [Complete Implementation Flow](#complete-implementation-flow)
- [API Details](#api-details)
- [Code Examples](#code-examples)
- [Error Handling](#error-handling)
- [Best Practices](#best-practices)
## Overview
Address input forms SHOULD implement this three-stage flow:
1. **Autocomplete** - Type-ahead suggestions as user types
2. **GetPlace** - Fetch complete details when user selects a suggestion
3. **Geocode** - Validate if user types complete address without selecting
## Complete Implementation Flow
```javascript
// Stage 1: User types in address field → Autocomplete
// Stage 2: User selects suggestion → GetPlace
// Stage 3: User submits without selection → Geocode
// HTML Structure
<input id="address-input" type="text" placeholder="Enter address">
<div id="suggestions"></div>
<button id="submit">Submit</button>
```
## API Details
### Autocomplete
**Purpose**: Provide type-ahead suggestions for partially typed addresses.
**When to use**: As user types in address input field (typically after 3+ characters).
**Critical**: Always display `Address.Label` to users, NOT `Title`. The `Title` field may show components in reverse order and is not suitable for display.
**Returns**: Array of suggestions, each with:
- `PlaceId` - Use this to fetch full details with GetPlace
- `Address.Label` - Display this to users (e.g., "123 Main St, Seattle, WA 98101")
- `Title` - DO NOT display (may be reversed: "98101, WA, Seattle, Main St, 123")
### GetPlace
**Purpose**: Retrieve complete place details after user selects autocomplete suggestion.
**When to use**: When user clicks/selects an autocomplete suggestion.
**Input**: `PlaceId` from Autocomplete result.
**Returns**: Complete place object with nested Address structure:
```javascript
{
PlaceId: "string",
Address: {
Label: "123 Main St, Seattle, WA 98101, USA",
Region: {
Code: "WA", // State/province code
Name: "Washington" // Full state name
},
Country: {
Code2: "US", // ISO 2-letter country code
Code3: "USA", // ISO 3-letter country code
Name: "United States" // Full country name
},
Locality: "Seattle", // City name (string)
PostalCode: "98101", // Postal code (string)
Street: "Main St", // Street name (string)
AddressNumber: "123" // Street number (string)
},
Position: [lon, lat] // Coordinates
}
```
**Important**: Address properties are nested objects, not flat strings. Access as `address.Region.Code`, not `address.RegionCode`.
### Geocode
**Purpose**: Validate and standardize a complete address typed by user (when they don't select autocomplete).
**When to use**: On form submission if user typed address but didn't select any autocomplete suggestion.
**Input**: Complete address string.
**Returns**: Array of matching addresses with coordinates and standardized formatting.
## Code Examples
### Complete Implementation
```javascript
// Initialize client
const authHelper = amazonLocationClient.withAPIKey(API_KEY, REGION);
const client = new amazonLocationClient.GeoPlacesClient(
authHelper.getClientConfig(),
);
let selectedPlaceId = null;
// Stage 1: Autocomplete as user types
document
.getElementById("address-input")
.addEventListener("input", async (e) => {
const query = e.target.value;
if (query.length < 3) {
document.getElementById("suggestions").innerHTML = "";
return;
}
try {
const command = new amazonLocationClient.places.AutocompleteCommand({
QueryText: query,
MaxResults: 5,
});
const response = await client.send(command);
// Display suggestions using Address.Label (NOT Title!)
const suggestionsHtml = response.ResultItems.map(
(item) =>
`<div class="suggestion" data-place-id="${item.Place?.PlaceId}">
${item.Address.Label}
</div>`,
).join("");
document.getElementById("suggestions").innerHTML = suggestionsHtml;
} catch (error) {
console.error("Autocomplete error:", error);
// Show error to user
}
});
// Stage 2: User selects suggestion → GetPlace
document.getElementById("suggestions").addEventListener("click", async (e) => {
if (!e.target.classList.contains("suggestion")) return;
const placeId = e.target.dataset.placeId;
selectedPlaceId = placeId;
try {
const command = new amazonLocationClient.places.GetPlaceCommand({
PlaceId: placeId,
});
const response = await client.send(command);
const place = response;
// Populate form with complete details
document.getElementById("address-input").value = place.Address.Label;
document.getElementById("street").value =
`${place.Address.AddressNumber || ""} ${place.Address.Street || ""}`.trim();
document.getElementById("city").value = place.Address.Locality || "";
document.getElementById("state").value = place.Address.Region?.Code || "";
document.getElementById("zip").value = place.Address.PostalCode || "";
document.getElementById("country").value =
place.Address.Country?.Code2 || "";
// Store coordinates for later use
document.getElementById("lat").value = place.Position[1];
document.getElementById("lon").value = place.Position[0];
// Clear suggestions
document.getElementById("suggestions").innerHTML = "";
} catch (error) {
console.error("GetPlace error:", error);
alert("Failed to fetch address details");
}
});
// Stage 3: User submits without selecting → Geocode
document.getElementById("submit").addEventListener("click", async () => {
if (selectedPlaceId) {
// User selected from autocomplete, proceed
submitForm();
return;
}
// User typed address without selecting, validate with Geocode
const query = document.getElementById("address-input").value;
if (!query) {
alert("Please enter an address");
return;
}
try {
const command = new amazonLocationClient.places.GeocodeCommand({
QueryText: query,
MaxResults: 1,
});
const response = await client.send(command);
if (response.ResultItems.length === 0) {
alert("Address not found. Please check and try again.");
return;
}
const result = response.ResultItems[0];
// Confirm with user if address was standardized
if (result.Address.Label !== query) {
const confirmed = confirm(`Did you mean: ${result.Address.Label}?`);
if (!confirmed) return;
}
// Populate with geocoded result
document.getElementById("lat").value = result.Position[1];
document.getElementById("lon").value = result.Position[0];
submitForm();
} catch (error) {
console.error("Geocode error:", error);
alert("Failed to validate address");
}
});
function submitForm() {
// Proceed with form submission
console.log("Submitting form with validated address");
}
```
## Error Handling
### Autocomplete Errors
- **Rate limiting**: Implement debouncing (300ms delay) before calling API
- **Network failures**: Show cached suggestions or friendly message
- **No results**: Allow user to continue typing or submit for geocoding
### GetPlace Errors
- **Invalid PlaceId**: Should not happen if using autocomplete results, but handle gracefully
- **Network timeout**: Retry once, then show error message
### Geocode Errors
- **No results**: Prompt user to check spelling or use autocomplete
- **Multiple results**: Present user with options to select correct one
- **Ambiguous address**: Ask for more details (e.g., city/state)
## Best Practices
### Performance
- **Debounce autocomplete**: Wait 300ms after user stops typing before API call
- **Cancel previous requests**: Cancel in-flight autocomplete requests when new input arrives
- **Cache results**: Cache autocomplete results for repeated queries
### User Experience
- **Always use Address.Label**: Never display the `Title` field - it may be reversed
- **Show visual feedback**: Loading indicators during API calls
- **Keyboard navigation**: Allow arrow keys and Enter to navigate suggestions
- **Clear affordances**: Make it obvious when address is validated
### Data Handling
- **Store PlaceId**: Keep PlaceId with address for future reference
- **Store coordinates**: Save Position for mapping and distance calculations
- **Handle missing fields**: Not all addresses have all components (e.g., AddressNumber)
- **Respect nested structure**: Access `address.Region.Code`, not `address.RegionCode`
### Accessibility
- **ARIA labels**: Use proper ARIA attributes for autocomplete
- **Keyboard support**: Full keyboard navigation for suggestions
- **Screen reader announcements**: Announce number of suggestions found
```
### references/address-verification.md
```markdown
# Address Verification
> **Audience Note**: Keywords MUST, SHOULD, MAY in this document indicate requirements for agent recommendations to users, following RFC 2119.
Validate and standardize addresses before persisting to databases or taking actions.
**Distinction from Address Input**: The address-input reference covers the UI/UX of collecting addresses from users (autocomplete, type-ahead). This reference covers validation and standardization of addresses AFTER collection, before storage or processing.
## Table of Contents
- [Overview](#overview)
- [When to Verify Addresses](#when-to-verify-addresses)
- [Basic Verification](#basic-verification)
- [Handling Verification Results](#handling-verification-results)
- [Complete Validation Flows](#complete-validation-flows)
- [Storage Considerations and Pricing](#storage-considerations-and-pricing)
- [Error Handling](#error-handling)
- [Best Practices](#best-practices)
## Overview
Address verification ensures data quality by:
1. **Validating** - Confirming address exists in postal database
2. **Standardizing** - Converting to canonical format (proper casing, abbreviations)
3. **Enriching** - Adding coordinates, postal codes, country codes
4. **Cleaning** - Fixing typos, removing ambiguity
Use `geo-places:Geocode` for address verification AFTER user has completed input.
## When to Verify Addresses
Verify addresses in these scenarios:
- **Before database insertion**: Ensure only valid, standardized addresses are stored
- **Before shipping/delivery**: Verify delivery address is valid and complete
- **Before API calls**: Validate address before passing to routing or distance APIs
- **Batch processing**: Clean existing address databases
- **Form submission**: Final validation before accepting user submission
- **Integration imports**: Validate addresses from external systems
Do NOT verify on every keystroke (use Autocomplete for that - see address-input reference).
## Basic Verification
### Single Address Verification
```javascript
const authHelper = amazonLocationClient.withAPIKey(API_KEY, REGION);
const client = new amazonLocationClient.GeoPlacesClient(authHelper.getClientConfig());
async function verifyAddress(addressString) {
try {
const command = new amazonLocationClient.places.GeocodeCommand({
QueryText: addressString,
MaxResults: 5 // Get multiple matches to detect ambiguity
});
const response = await client.send(command);
if (response.ResultItems.length === 0) {
return {
valid: false,
reason: 'ADDRESS_NOT_FOUND',
message: 'Address not found in postal database'
};
}
const topResult = response.ResultItems[0];
// Check for ambiguous input (multiple strong matches)
const isAmbiguous = response.ResultItems.length > 1 &&
response.ResultItems[1].Address.Label.toLowerCase() !== topResult.Address.Label.toLowerCase();
return {
valid: true,
ambiguous: isAmbiguous,
standardized: topResult.Address,
coordinates: {
lat: topResult.Position[1],
lon: topResult.Position[0]
},
originalInput: addressString,
allMatches: response.ResultItems
};
} catch (error) {
console.error('Verification error:', error);
return {
valid: false,
reason: 'VERIFICATION_ERROR',
message: error.message
};
}
}
// Usage
const result = await verifyAddress("500 E 4th St, Austin, TX 78701");
if (result.valid) {
console.log('Valid address:', result.standardized.Label);
console.log('Coordinates:', result.coordinates);
} else {
console.log('Invalid:', result.message);
}
```
### Structured Address Verification
```javascript
// Verify address from form fields
async function verifyStructuredAddress(fields) {
// Build address string from components
const parts = [
fields.street,
fields.city,
fields.state,
fields.postalCode,
fields.country
].filter(Boolean);
const addressString = parts.join(', ');
return await verifyAddress(addressString);
}
// Usage
const formData = {
street: "500 E 4th St",
city: "Austin",
state: "CA",
postalCode: "94043",
country: "USA"
};
const verification = await verifyStructuredAddress(formData);
```
## Handling Verification Results
### Perfect Match
```javascript
if (result.valid && !result.ambiguous) {
// Address is valid and unambiguous
// Store standardized version
await saveAddress({
original: result.originalInput,
standardized: result.standardized.Label,
street: result.standardized.Street,
addressNumber: result.standardized.AddressNumber,
locality: result.standardized.Locality,
region: result.standardized.Region?.Code,
postalCode: result.standardized.PostalCode,
country: result.standardized.Country?.Code2,
latitude: result.coordinates.lat,
longitude: result.coordinates.lon,
verified: true,
verifiedAt: new Date().toISOString()
});
}
```
### Address Not Found
```javascript
if (!result.valid && result.reason === 'ADDRESS_NOT_FOUND') {
// Show error to user
showError(
'We could not verify this address. Please check for typos and try again.'
);
// Offer suggestions
if (result.originalInput.length > 10) {
// Suggest using autocomplete
showSuggestion(
'Try using the address autocomplete field for better results.'
);
}
// Still allow submission if user insists
showOption(
'Submit anyway (address will be marked as unverified)',
async () => {
await saveAddress({
original: result.originalInput,
verified: false,
verifiedAt: null
});
}
);
}
```
### Ambiguous Address
```javascript
if (result.valid && result.ambiguous) {
// Present user with options
showAmbiguityDialog(
'We found multiple matches for this address. Please select the correct one:',
result.allMatches.map(match => ({
label: match.Address.Label,
details: {
locality: match.Address.Locality,
region: match.Address.Region?.Name,
postalCode: match.Address.PostalCode
},
onSelect: async () => {
await saveAddress({
standardized: match.Address.Label,
latitude: match.Position[1],
longitude: match.Position[0],
verified: true
});
}
}))
);
}
```
### Standardization Differences
```javascript
// Check if geocoded address differs from input
if (result.valid) {
const inputNormalized = result.originalInput.toLowerCase().trim();
const standardizedNormalized = result.standardized.Label.toLowerCase().trim();
if (inputNormalized !== standardizedNormalized) {
// Show user the standardized version
const confirmed = await confirmDialog(
'We found a slight difference in the address:',
{
original: result.originalInput,
standardized: result.standardized.Label,
question: 'Use the standardized version?'
}
);
if (confirmed) {
// Use standardized
await saveAddress({
standardized: result.standardized.Label,
...result.coordinates,
verified: true
});
} else {
// Keep original but mark as manually confirmed
await saveAddress({
original: result.originalInput,
...result.coordinates,
verified: true,
manuallyConfirmed: true
});
}
}
}
```
## Complete Validation Flows
### Pre-Submission Validation
```javascript
// Validate form before submission
async function validateAddressForm(formElement) {
const submitButton = formElement.querySelector('button[type="submit"]');
const statusDiv = document.getElementById('validation-status');
formElement.addEventListener('submit', async (e) => {
e.preventDefault();
// Show loading state
submitButton.disabled = true;
statusDiv.innerHTML = 'Verifying address...';
// Get address from form
const addressInput = formElement.querySelector('#address').value;
try {
const result = await verifyAddress(addressInput);
if (!result.valid) {
statusDiv.innerHTML = `
<div class="error">
<strong>Address verification failed:</strong>
<p>${result.message}</p>
<button onclick="submitAnyway()">Submit Anyway</button>
<button onclick="editAddress()">Edit Address</button>
</div>
`;
submitButton.disabled = false;
return;
}
if (result.ambiguous) {
statusDiv.innerHTML = '<div class="warning">Multiple matches found. Please select:</div>';
showAmbiguityOptions(result.allMatches);
submitButton.disabled = false;
return;
}
// Update form with standardized address
if (result.standardized.Label !== addressInput) {
const useStandardized = confirm(
`Use standardized address?\n\nYou entered:\n${addressInput}\n\nStandardized:\n${result.standardized.Label}`
);
if (useStandardized) {
formElement.querySelector('#address').value = result.standardized.Label;
}
}
// Store coordinates in hidden fields
formElement.querySelector('#latitude').value = result.coordinates.lat;
formElement.querySelector('#longitude').value = result.coordinates.lon;
// Mark as verified
formElement.querySelector('#verified').value = 'true';
// Submit form
statusDiv.innerHTML = '<div class="success">Address verified!</div>';
formElement.submit();
} catch (error) {
statusDiv.innerHTML = `
<div class="error">
Verification error: ${error.message}
</div>
`;
submitButton.disabled = false;
}
});
}
```
### Batch Verification
```javascript
// Verify multiple addresses (e.g., cleaning existing database)
async function batchVerifyAddresses(addresses) {
const results = [];
for (const address of addresses) {
try {
const result = await verifyAddress(address.text);
results.push({
id: address.id,
originalAddress: address.text,
verified: result.valid,
standardized: result.valid ? result.standardized.Label : null,
coordinates: result.valid ? result.coordinates : null,
ambiguous: result.ambiguous,
error: result.valid ? null : result.message
});
// Rate limiting - wait between requests
await new Promise(resolve => setTimeout(resolve, 100));
} catch (error) {
results.push({
id: address.id,
originalAddress: address.text,
verified: false,
error: error.message
});
}
// Progress indicator
console.log(`Verified ${results.length}/${addresses.length}`);
}
return results;
}
// Generate report
function generateVerificationReport(results) {
const stats = {
total: results.length,
verified: results.filter(r => r.verified && !r.ambiguous).length,
ambiguous: results.filter(r => r.ambiguous).length,
invalid: results.filter(r => !r.verified).length,
errors: results.filter(r => r.error).length
};
console.log('Verification Report:', stats);
return {
stats,
needsReview: results.filter(r => !r.verified || r.ambiguous)
};
}
// Usage
const addressesToVerify = [
{ id: 1, text: "500 E 4th St, Austin, TX 78701" },
{ id: 2, text: "301 Congress Ave, Austin, TX 78701" },
// ... more addresses
];
const verificationResults = await batchVerifyAddresses(addressesToVerify);
const report = generateVerificationReport(verificationResults);
```
## Storage Considerations and Pricing
**IMPORTANT**: How you store Places API results affects pricing. Review the [Places API Stored Pricing documentation](https://docs.aws.amazon.com/location/latest/developerguide/places-pricing.html#stored-pricing) before implementing storage.
### Pricing Tiers Based on Storage
Amazon Location Places API has different pricing based on the `IntendedUse` parameter:
**Stored Pricing** (`IntendedUse: "Storage"`):
- ✅ Can store results indefinitely
- ✅ Supports all features (Label, Core, Advanced)
- ✅ Price cap - maximum cost per API call
- 💰 Higher per-request cost
- **Use when**: Building applications that cache results long-term or perform analysis on historical data
**Other Pricing Tiers**:
- ⚠️ Label & Core: Results cannot be stored permanently
- ⚠️ Advanced: Results can be cached temporarily but not stored indefinitely
- 💰 Lower per-request cost
- **Use when**: Real-time lookups without long-term storage needs
### Setting Stored Pricing
```javascript
// To enable stored pricing (allows indefinite storage)
const command = new amazonLocationClient.places.GeocodeCommand({
QueryText: addressString,
IntendedUse: "Storage", // Required for long-term storage
MaxResults: 1
});
```
### Storage Decision Guide
Ask yourself:
1. **Do you need to store results indefinitely?**
- YES → Use `IntendedUse: "Storage"` and pay stored pricing
- NO → Use default pricing, don't store permanently
2. **Will you reuse results to reduce API calls?**
- YES → Consider stored pricing for cost-effectiveness over time
- NO → Use real-time lookups with default pricing
3. **Are you building analytics on historical place data?**
- YES → Use `IntendedUse: "Storage"`
- NO → Use default pricing
## Error Handling
### Validation Error Types
```javascript
async function verifyAddressWithDetailedErrors(addressString) {
try {
const result = await verifyAddress(addressString);
if (!result.valid) {
// Classify error
if (addressString.length < 10) {
return {
valid: false,
errorCode: 'TOO_SHORT',
message: 'Address is too short. Please provide a complete address.',
suggestion: 'Include street, city, and state/postal code.'
};
}
if (!/\d/.test(addressString)) {
return {
valid: false,
errorCode: 'MISSING_NUMBER',
message: 'Address appears to be missing a street number.',
suggestion: 'Add the building or house number.'
};
}
if (!/[A-Z]{2}/.test(addressString.toUpperCase())) {
return {
valid: false,
errorCode: 'MISSING_STATE',
message: 'Please include the state or province.',
suggestion: 'Add the 2-letter state code (e.g., CA, NY).'
};
}
return {
valid: false,
errorCode: 'NOT_FOUND',
message: 'Address not found in postal database.',
suggestion: 'Check for typos or try entering in a different format.'
};
}
return result;
} catch (error) {
return {
valid: false,
errorCode: 'VERIFICATION_FAILED',
message: 'Unable to verify address due to technical error.',
technicalError: error.message
};
}
}
```
## Best Practices
### When to Verify
- **Always verify before storage**: Prevent bad data from entering database
- **Verify before critical actions**: Shipping, delivery, routing
- **Don't verify on every keystroke**: Use autocomplete during input, geocode at submission
- **Batch verification**: For data imports, verify in batches with rate limiting
### Data Quality
- **Understand pricing implications**: Review stored pricing before implementing long-term storage
- **Use IntendedUse parameter**: Set `IntendedUse: "Storage"` if storing results indefinitely
- **Consider caching vs storage**: Temporary session caching doesn't require stored pricing
- **Handle missing components**: Not all addresses have all fields (street number, etc.)
### User Experience
- **Show what changed**: When standardizing, show user the difference
- **Allow manual override**: Let users keep their version if they insist
- **Handle ambiguity gracefully**: Present options, don't guess
- **Provide helpful errors**: Explain WHY address is invalid and how to fix
### Performance
- **Cache results**: Cache verification results temporarily within your session
- **Rate limit batch jobs**: Wait 100ms between batch verifications
- **Verify once per session**: Don't re-verify address user already confirmed
- **Use autocomplete first**: Preferred over post-hoc verification
```
### references/calculate-routes.md
```markdown
# Calculate Routes
> **Audience Note**: Keywords MUST, SHOULD, MAY in this document indicate requirements for agent recommendations to users, following RFC 2119.
Calculate routes between locations using Amazon Location Service Routes API and display them on interactive maps.
## Table of Contents
- [Overview](#overview)
- [Authentication](#authentication)
- [Basic Route Calculation](#basic-route-calculation)
- [Displaying Routes on Maps](#displaying-routes-on-maps)
- [Request Parameters](#request-parameters)
- [Response Structure](#response-structure)
- [Travel Modes and Options](#travel-modes-and-options)
- [Waypoints and Multi-Stop Routes](#waypoints-and-multi-stop-routes)
- [Complete Examples](#complete-examples)
- [Best Practices](#best-practices)
## Overview
The Amazon Location Routes API ([`geo-routes:CalculateRoutes`](https://docs.aws.amazon.com/location/latest/APIReference/API_CalculateRoutes.html)) calculates optimal routes between two or more locations with support for multiple travel modes, route preferences, and detailed navigation instructions.
**Key capabilities:**
- Calculate routes with turn-by-turn directions
- Support for car, truck, scooter, and pedestrian travel modes
- Avoid specific features (tolls, ferries, motorways)
- Include traffic data in route calculations
- Calculate toll costs
- Retrieve speed limits along the route
- Optimize for fastest or shortest routes
**When to use CalculateRoutes:**
- Providing driving or walking directions
- Building navigation applications
- Displaying routes on maps
- Estimating travel time and distance
- Planning multi-stop journeys with waypoints
**Related APIs:**
- `CalculateRouteMatrix` - For multiple origin/destination travel times (batch calculations)
- `CalculateIsolines` - For service areas (all locations reachable within time/distance)
- `OptimizeWaypoints` - For solving traveling salesman problem (optimal waypoint order)
## Authentication
All examples in this guide use the Amazon Location authentication helper to configure clients. The examples use the **modular npm approach** with ES6 imports.
**Quick setup:**
```javascript
import {
GeoRoutesClient,
CalculateRoutesCommand,
} from "@aws-sdk/client-geo-routes";
import { withAPIKey } from "@aws/amazon-location-utilities-auth-helper";
// Create authentication helper and client
const authHelper = withAPIKey("your-api-key", "us-west-2");
const client = new GeoRoutesClient(authHelper.getClientConfig());
// Make requests (no Key parameter needed)
const response = await client.send(
new CalculateRoutesCommand({
Origin: [-97.7431, 30.2672],
Destination: [-97.7723, 30.2672],
TravelMode: "Car",
}),
);
```
**For detailed authentication guidance**, including:
- Browser vs npm/modular approaches
- Bundled client usage (`amazonLocationClient.*`)
- Choosing between API keys, Cognito, and custom credential providers
- Security best practices and when to use each method
- Complete examples for React, Vue, and plain HTML
**See the web-javascript reference documentation.**
## Basic Route Calculation
### Minimal Route Request
The simplest route calculation requires only origin and destination coordinates:
```javascript
import {
GeoRoutesClient,
CalculateRoutesCommand,
} from "@aws-sdk/client-geo-routes";
import { withAPIKey } from "@aws/amazon-location-utilities-auth-helper";
// Create an authentication helper instance using an API key
const authHelper = withAPIKey("your-api-key", "us-west-2");
// Configure the client to use API keys when making supported requests
const client = new GeoRoutesClient(authHelper.getClientConfig());
const params = {
Origin: [-97.7431, 30.2672], // [longitude, latitude] - Downtown Austin
Destination: [-97.7723, 30.2672], // [longitude, latitude] - Zilker Park
TravelMode: "Car", // Required: Car, Truck, Scooter, Pedestrian
};
const command = new CalculateRoutesCommand(params);
const response = await client.send(command);
console.log(`Distance: ${response.Routes[0].Summary.Distance} meters`);
console.log(`Duration: ${response.Routes[0].Summary.Duration} seconds`);
```
### Understanding Coordinates
Coordinates MUST be specified as `[longitude, latitude]` arrays:
- **Longitude** comes first (range: -180 to 180, negative = west, positive = east)
- **Latitude** comes second (range: -90 to 90, negative = south, positive = north)
This matches GeoJSON format and is consistent across all Amazon Location Service APIs.
### Travel Modes
Each travel mode affects route calculation differently:
| Mode | Use Case | Route Characteristics |
| -------------- | --------------------------- | ------------------------------------------------------ |
| **Car** | Standard passenger vehicles | Uses roads accessible to cars, considers traffic |
| **Truck** | Commercial vehicles | Accounts for truck restrictions, weight limits, hazmat |
| **Scooter** | Motorized scooters | Uses roads with lower speed limits, avoids highways |
| **Pedestrian** | Walking directions | Uses sidewalks, crosswalks, pedestrian paths |
Choose the travel mode that matches your user's actual mode of transportation for accurate routes and travel times.
## Displaying Routes on Maps
### Complete Route Visualization Example
This example calculates a route and draws it on a MapLibre map with styled lines, markers for start/end points, and automatic viewport fitting:
```javascript
// HTML setup (include in <head>)
// <link href="https://unpkg.com/maplibre-gl@5/dist/maplibre-gl.css" rel="stylesheet">
// <script src="https://cdn.jsdelivr.net/npm/maplibre-gl@5"></script>
import {
GeoRoutesClient,
CalculateRoutesCommand,
} from "@aws-sdk/client-geo-routes";
import { withAPIKey } from "@aws/amazon-location-utilities-auth-helper";
const API_KEY = "your-api-key";
const REGION = "us-west-2";
// Create an authentication helper instance
const authHelper = withAPIKey(API_KEY, REGION);
// Initialize map
const styleUrl = `https://maps.geo.${REGION}.amazonaws.com/v2/styles/Standard/descriptor?key=${API_KEY}`;
const map = new maplibregl.Map({
container: "map",
style: styleUrl,
center: [-97.7431, 30.2672],
zoom: 12,
validateStyle: false,
});
async function calculateAndDisplayRoute(origin, destination) {
// Configure the client with authentication
const client = new GeoRoutesClient(authHelper.getClientConfig());
const params = {
Origin: origin,
Destination: destination,
TravelMode: "Car",
LegGeometryFormat: "Simple", // Returns coordinate arrays for mapping
};
const command = new CalculateRoutesCommand(params);
const response = await client.send(command);
const route = response.Routes[0];
const leg = route.Legs[0];
// Extract route coordinates
const routeCoordinates = leg.Geometry.LineString;
// Wait for map to load
map.on("load", () => {
// Add route line to map
map.addSource("route", {
type: "geojson",
data: {
type: "Feature",
geometry: {
type: "LineString",
coordinates: routeCoordinates,
},
},
});
// Style the route line
map.addLayer({
id: "route-line",
type: "line",
source: "route",
layout: {
"line-join": "round",
"line-cap": "round",
},
paint: {
"line-color": "#3b82f6", // Blue route line
"line-width": 5,
"line-opacity": 0.8,
},
});
// Add start marker (green)
new maplibregl.Marker({ color: "#22c55e" })
.setLngLat(origin)
.setPopup(new maplibregl.Popup().setHTML("<strong>Start</strong>"))
.addTo(map);
// Add end marker (red)
new maplibregl.Marker({ color: "#ef4444" })
.setLngLat(destination)
.setPopup(new maplibregl.Popup().setHTML("<strong>Destination</strong>"))
.addTo(map);
// Fit map to show entire route
const bounds = routeCoordinates.reduce(
(bounds, coord) => {
return bounds.extend(coord);
},
new maplibregl.LngLatBounds(routeCoordinates[0], routeCoordinates[0]),
);
map.fitBounds(bounds, {
padding: { top: 50, bottom: 50, left: 50, right: 50 },
});
});
// Display route summary
const distanceKm = (route.Summary.Distance / 1000).toFixed(1);
const durationMin = Math.round(route.Summary.Duration / 60);
console.log(`Route: ${distanceKm} km, ${durationMin} minutes`);
return route;
}
// Usage
const origin = [-97.7431, 30.2672]; // Downtown Austin
const destination = [-97.7723, 30.2672]; // Zilker Park
calculateAndDisplayRoute(origin, destination);
```
### Choosing Between Simple and FlexiblePolyline Geometry Formats
The `LegGeometryFormat` parameter determines how route geometry is returned. Understanding when to use each format is crucial for optimizing your application's performance, user experience, and data costs.
#### Format Overview
**Simple Format** returns coordinate arrays ready for immediate use:
```javascript
Geometry: {
LineString: [
[-97.7431, 30.2672],
[-97.7445, 30.2678],
[-97.7459, 30.2684],
// ... hundreds more coordinates
];
}
```
**FlexiblePolyline Format** returns compressed encoded strings:
```javascript
Geometry: {
LineString: "BFoz5xJ67i1BU1B7PzIhaxL7Y"; // Requires decoding
}
```
#### Response Size Comparison
For a typical 10km urban route with 400 coordinate points:
- **Simple**: ~18-25 KB (raw JSON coordinate arrays)
- **FlexiblePolyline**: ~2-4 KB (compressed encoded string)
Bandwidth savings: 5-10x smaller with FlexiblePolyline.
The size difference becomes more significant with longer routes or multiple route calculations.
#### When to Use Simple Format
Use `Simple` format when convenience and development speed matter more than bandwidth optimization:
##### 1. Web applications with good connectivity
```javascript
// Desktop users on broadband/WiFi - bandwidth isn't a concern
const params = {
Origin: [-97.7431, 30.2672],
Destination: [-97.7723, 30.2672],
TravelMode: "Car",
LegGeometryFormat: "Simple", // No decoder needed, works immediately
};
```
Desktop and laptop users typically have reliable high-speed connections where a few extra KB don't impact user experience.
##### 2. Prototyping and development
- Faster iteration without setting up decoder libraries
- Easier debugging (inspect coordinates directly in response)
- Quick POCs and demos where optimization isn't critical
- During development before production optimization
##### 3. Single route calculations
```javascript
// Calculating one route per session - minimal data transfer
async function showDirections(origin, destination) {
const response = await calculateRoute(origin, destination, "Simple");
displayOnMap(response.Routes[0].Legs[0].Geometry.LineString);
}
```
When users request only occasional routes (e.g., "Get directions" button), the one-time bandwidth cost is negligible.
##### 4. Server-side processing
```javascript
// Backend service calculating routes for internal use
app.post("/api/calculate-route", async (req, res) => {
const route = await calculateRoute(
req.body.origin,
req.body.destination,
"Simple", // Fast AWS network, immediate coordinate access
);
// Process coordinates directly without decoding
const simplified = simplifyRoute(route.Legs[0].Geometry.LineString);
res.json(simplified);
});
```
Server-to-server communication within AWS benefits from fast regional networks, and coordinate processing is easier with the Simple format.
##### 5. When manipulating route coordinates
```javascript
// Need to modify coordinates programmatically
const coordinates = leg.Geometry.LineString;
// Add waypoint markers every 1km
const markers = coordinates.filter((_, i) => i % 50 === 0);
// Simplify geometry for overview display
const simplified = coordinates.filter((_, i) => i % 5 === 0);
// Extract bounding box
const bounds = coordinates.reduce(
(bbox, [lng, lat]) => {
return {
minLng: Math.min(bbox.minLng, lng),
maxLng: Math.max(bbox.maxLng, lng),
minLat: Math.min(bbox.minLat, lat),
maxLat: Math.max(bbox.maxLat, lat),
};
},
{ minLng: Infinity, maxLng: -Infinity, minLat: Infinity, maxLat: -Infinity },
);
```
#### When to Use FlexiblePolyline Format
Use `FlexiblePolyline` format when bandwidth efficiency matters for user experience or costs.
**Note:** Decoding FlexiblePolyline requires the [`@aws/polyline`](https://github.com/aws-geospatial/polyline) library. See [Using FlexiblePolyline with @aws/polyline](#using-flexiblepolyline-with-awspolyline) below for installation instructions.
##### 1. Mobile applications
```javascript
// Mobile users on cellular networks - minimize data usage
const params = {
Origin: origin,
Destination: destination,
TravelMode: "Car",
LegGeometryFormat: "FlexiblePolyline", // 5-10x smaller responses
};
```
**Why this matters for mobile:**
- Cellular data is often metered/expensive (especially international roaming)
- Slower network speeds benefit from smaller payloads
- Battery conservation (less data transfer = less radio usage)
- Better experience in areas with poor connectivity
##### 2. Real-time navigation with frequent rerouting
```javascript
// Recalculating routes every 2 minutes based on traffic
setInterval(async () => {
const updatedRoute = await client.send(
new CalculateRoutesCommand({
Origin: currentLocation,
Destination: destination,
TravelMode: "Car",
LegGeometryFormat: "FlexiblePolyline", // Fast updates with minimal data
}),
);
updateMapRoute(
polyline.decodeToLineStringFeature(
updatedRoute.Routes[0].Legs[0].Geometry.LineString,
),
);
}, 120000);
```
Frequent route updates multiply bandwidth costs - FlexiblePolyline keeps data transfer manageable.
##### 3. Multiple route calculations
```javascript
// Comparing 5 alternative routes - bandwidth adds up quickly
// Simple format: 100-125 KB total
// FlexiblePolyline: 10-20 KB total (10x savings)
const alternativeRoutes = await Promise.all([
calculateRoute(origin, destination, { avoid: "Tolls" }),
calculateRoute(origin, destination, { optimize: "ShortestRoute" }),
calculateRoute(origin, destination, { avoid: "Ferries" }),
calculateRoute(origin, destination, { travelMode: "Pedestrian" }),
calculateRoute(origin, destination, { travelMode: "Scooter" }),
]);
// Decode all routes for display
const decodedRoutes = alternativeRoutes.map((route) =>
polyline.decodeToLineStringFeature(route.Legs[0].Geometry.LineString),
);
```
##### 4. Caching routes for offline use
```javascript
// Store routes in IndexedDB or localStorage
// FlexiblePolyline saves 80-90% storage space
async function cacheRoute(routeId, origin, destination) {
const response = await client.send(
new CalculateRoutesCommand({
Origin: origin,
Destination: destination,
TravelMode: "Car",
LegGeometryFormat: "FlexiblePolyline", // Efficient storage
}),
);
// Store compressed format
const cachedData = {
routeId,
encodedGeometry: response.Routes[0].Legs[0].Geometry.LineString,
summary: response.Routes[0].Summary,
timestamp: Date.now(),
};
await db.routes.put(cachedData);
}
// Retrieve and decode when needed
async function loadCachedRoute(routeId) {
const cached = await db.routes.get(routeId);
return polyline.decodeToLineStringFeature(cached.encodedGeometry);
}
```
##### 5. Progressive Web Apps (PWAs)
```javascript
// Offline-capable apps that sync route data
// Smaller payloads = faster sync, less storage
self.addEventListener("sync", async (event) => {
if (event.tag === "sync-routes") {
event.waitUntil(syncRoutes());
}
});
async function syncRoutes() {
const pendingRoutes = await getPendingRoutes();
// Calculate all routes with compressed format
const results = await Promise.all(
pendingRoutes.map((r) =>
calculateRoute(r.origin, r.destination, {
LegGeometryFormat: "FlexiblePolyline", // Efficient sync
}),
),
);
// Store compressed for offline use
await storeRoutesLocally(results);
}
```
##### 6. Batch operations and analytics
```javascript
// Calculating hundreds of routes for analysis
// FlexiblePolyline makes large-scale operations practical
async function analyzeDeliveryRoutes(stops) {
const routes = [];
for (let i = 0; i < stops.length - 1; i++) {
const response = await client.send(
new CalculateRoutesCommand({
Origin: stops[i],
Destination: stops[i + 1],
LegGeometryFormat: "FlexiblePolyline", // Keep total data manageable
}),
);
routes.push({
segment: `${i} -> ${i + 1}`,
distance: response.Routes[0].Summary.Distance,
duration: response.Routes[0].Summary.Duration,
geometry: response.Routes[0].Legs[0].Geometry.LineString,
});
}
return routes;
}
```
#### Using FlexiblePolyline with @aws/polyline
When using FlexiblePolyline format, decode with the official AWS polyline library:
**Installation:**
```bash
npm install @aws/polyline
```
Or use CDN for browser applications:
```html
<script src="https://cdn.jsdelivr.net/npm/@aws/polyline/dist/polyline.min.js"></script>
```
**Decoding routes for MapLibre:**
```javascript
import {
GeoRoutesClient,
CalculateRoutesCommand,
} from "@aws-sdk/client-geo-routes";
import { withAPIKey } from "@aws/amazon-location-utilities-auth-helper";
import * as polyline from "@aws/polyline";
// Create an authentication helper instance
const authHelper = withAPIKey("your-api-key", "us-west-2");
const client = new GeoRoutesClient(authHelper.getClientConfig());
async function calculateAndDisplayRoute(origin, destination) {
// Request FlexiblePolyline format
const response = await client.send(
new CalculateRoutesCommand({
Origin: origin,
Destination: destination,
TravelMode: "Car",
LegGeometryFormat: "FlexiblePolyline",
}),
);
const encodedGeometry = response.Routes[0].Legs[0].Geometry.LineString;
// Decode to GeoJSON Feature - ready for MapLibre
const geoJsonFeature = polyline.decodeToLineStringFeature(encodedGeometry);
// Add directly to map
map.addSource("route", {
type: "geojson",
data: geoJsonFeature, // Feature with LineString geometry
});
map.addLayer({
id: "route-line",
type: "line",
source: "route",
paint: {
"line-color": "#3b82f6",
"line-width": 5,
"line-opacity": 0.8,
},
});
// Access coordinates if needed for bounds calculation
const coordinates = geoJsonFeature.geometry.coordinates;
fitMapToBounds(coordinates);
}
```
**Alternative decode methods:**
```javascript
// Get raw coordinate array instead of GeoJSON Feature
const coordinates = polyline.decodeToLngLatArray(encodedGeometry);
// Returns: [[lng, lat], [lng, lat], ...]
// Get GeoJSON LineString geometry only (no Feature wrapper)
const geometry = polyline.decodeToLineString(encodedGeometry);
// Returns: { type: "LineString", coordinates: [[lng, lat], ...] }
```
**Performance characteristics:**
- Decoding time: ~1-5ms for typical routes (negligible)
- Library size: ~15 KB minified (small overhead)
- Net benefit: Bandwidth savings far exceed CPU cost
#### Decision Matrix
| Scenario | Recommended Format | Primary Reason |
| ---------------------------------- | -------------------- | -------------------------------- |
| Desktop web app, single route | **Simple** | Convenience > bandwidth |
| Mobile app with navigation | **FlexiblePolyline** | Data costs and battery |
| Prototyping/POC | **Simple** | Faster development |
| PWA with offline support | **FlexiblePolyline** | Storage efficiency |
| Enterprise internal dashboard | **Simple** | Fast network, easier debugging |
| Delivery optimization (10+ routes) | **FlexiblePolyline** | Cumulative bandwidth savings |
| Real-time rerouting every 2min | **FlexiblePolyline** | Minimize repeated data transfer |
| One-time "Get Directions" feature | **Simple** | Single request, no decoder setup |
| Route caching in localStorage | **FlexiblePolyline** | 80-90% storage savings |
| Server-side route processing | **Simple** | Direct coordinate access |
| International users on roaming | **FlexiblePolyline** | Expensive cellular data |
| Route analytics/batch jobs | **FlexiblePolyline** | Large-scale data handling |
#### Hybrid Strategy
Some applications benefit from using both formats strategically:
```javascript
class RouteService {
constructor(isMobile, hasOfflineMode) {
this.isMobile = isMobile;
this.hasOfflineMode = hasOfflineMode;
}
async calculateRoute(origin, destination, forDisplay = true) {
// Choose format based on context
const format = this.shouldUseCompression(forDisplay)
? "FlexiblePolyline"
: "Simple";
const response = await client.send(
new CalculateRoutesCommand({
Origin: origin,
Destination: destination,
TravelMode: "Car",
LegGeometryFormat: format,
}),
);
// Cache compressed version if offline mode enabled
if (this.hasOfflineMode && format === "FlexiblePolyline") {
await this.cacheRoute(origin, destination, response);
}
// Decode if necessary
const geometry =
format === "FlexiblePolyline"
? polyline.decodeToLineStringFeature(
response.Routes[0].Legs[0].Geometry.LineString,
)
: this.convertToGeoJSON(response.Routes[0].Legs[0].Geometry.LineString);
return { route: response.Routes[0], geometry };
}
shouldUseCompression(forDisplay) {
// Always compress for mobile
if (this.isMobile) return true;
// Always compress for offline caching
if (this.hasOfflineMode) return true;
// Use Simple for desktop display (convenience)
return !forDisplay;
}
convertToGeoJSON(coordinates) {
return {
type: "Feature",
geometry: {
type: "LineString",
coordinates,
},
};
}
}
// Usage
const routeService = new RouteService(isMobileDevice(), hasOfflineFeature());
const { route, geometry } = await routeService.calculateRoute(
origin,
destination,
);
displayOnMap(geometry);
```
#### Summary Guidelines
**Use Simple when:**
- ✅ Building for desktop/WiFi users
- ✅ Calculating single routes per session
- ✅ Prototyping or developing
- ✅ Need direct coordinate manipulation
- ✅ Server-side processing
**Use FlexiblePolyline when:**
- ✅ Building mobile applications
- ✅ Calculating multiple routes
- ✅ Real-time navigation with frequent updates
- ✅ Caching routes offline
- ✅ Operating in bandwidth-constrained environments
- ✅ Batch operations or analytics
**The tradeoff:** Simple format offers convenience (no decoder needed), while FlexiblePolyline offers efficiency (5-10x smaller). Choose based on whether your users will notice the bandwidth difference.
### Styling Route Lines
Customize route appearance using MapLibre paint properties:
```javascript
map.addLayer({
id: "route-line",
type: "line",
source: "route",
paint: {
"line-color": "#3b82f6", // Route color
"line-width": 5, // Line thickness (pixels)
"line-opacity": 0.8, // Transparency (0-1)
"line-dasharray": [2, 2], // Optional: dashed line pattern
},
});
```
**Recommended styling patterns:**
- **Primary route**: Solid blue line, 5-6px width, 0.8 opacity
- **Alternative routes**: Dashed gray line, 3-4px width, 0.6 opacity
- **Selected route**: Brighter color, 7-8px width, 1.0 opacity
## Request Parameters
### Essential Parameters
| Parameter | Type | Required | Description |
| ------------- | ------------ | ----------- | ------------------------------------------------ |
| `Origin` | `[lng, lat]` | Yes | Starting position |
| `Destination` | `[lng, lat]` | Yes | Ending position |
| `TravelMode` | String | Yes | `Car`, `Truck`, `Scooter`, or `Pedestrian` |
| `Key` | String | Conditional | API key for resourceless operations (or use IAM) |
### Optimization Parameters
| Parameter | Default | Purpose |
| -------------------- | ------------------ | ---------------------------------------- |
| `OptimizeRoutingFor` | `FastestRoute` | Choose `FastestRoute` or `ShortestRoute` |
| `LegGeometryFormat` | `FlexiblePolyline` | Use `Simple` for map display |
**When to optimize for shortest:**
- Walking/pedestrian routes where distance matters more than time
- Delivery routes with distance-based costs
- Scenic routes where users prefer less highway driving
**When to optimize for fastest:**
- Time-sensitive navigation (default)
- Commuting and business travel
- Emergency or urgent deliveries
### Route Preferences (Avoid)
The `Avoid` parameter lets you exclude specific route features:
```javascript
const params = {
Origin: [-97.7431, 30.2672],
Destination: [-97.7723, 30.2672],
TravelMode: "Car",
Avoid: {
Tolls: true, // Avoid toll roads
Ferries: true, // Avoid ferries
CarShuttleTrains: true, // Avoid car shuttle trains
},
};
```
**Important**: Avoidances are treated as preferences, not hard constraints. If no alternative route exists, the API returns a route that includes the avoided feature. Always check route properties to confirm if avoided features are present.
### Additional Features
Request extra data in the response:
```javascript
const params = {
// ... other params
LegAdditionalFeatures: ["Summary", "Tolls", "TravelStepInstructions"],
SpanAdditionalFeatures: ["SpeedLimit", "RoadName"],
};
```
**LegAdditionalFeatures:**
- `Summary` - Total distance, duration, and route-level statistics
- `Tolls` - Toll costs and systems along the route
- `TravelStepInstructions` - Turn-by-turn navigation instructions
**SpanAdditionalFeatures:**
- `SpeedLimit` - Posted speed limits along route segments
- `RoadName` - Road names for each segment
- `Regions` - Administrative regions (city, state, country)
Request these features only when needed since they increase response size and processing time.
## Response Structure
### Routes Array
The API returns an array of route options (typically one route):
```javascript
{
Routes: [
{
Summary: {
Distance: 7845, // Total distance in meters
Duration: 1023, // Total duration in seconds
RouteBBox: [
// Bounding box [minLng, minLat, maxLng, maxLat]
-97.7723, 30.2672, -97.7431, 30.2672,
],
},
Legs: [
// Array of route segments
{
Geometry: {
LineString: [
// Array of [lng, lat] coordinates
[-97.7431, 30.2672],
[-97.7445, 30.2678],
// ... more coordinates
],
},
Summary: {
/* leg-specific summary */
},
TravelSteps: [
/* navigation instructions */
],
},
],
},
];
}
```
### Understanding Legs
Routes are divided into **legs** - segments between waypoints:
- **No waypoints**: 1 leg (origin to destination)
- **N waypoints**: N+1 legs (origin → waypoint₁ → waypoint₂ → ... → destination)
Each leg contains its own geometry, distance, duration, and travel steps.
### Travel Steps (Turn-by-Turn Instructions)
When `TravelStepInstructions` is requested:
```javascript
TravelSteps: [
{
Type: "Turn",
Instruction: "Turn right onto Pine St",
Distance: 150, // Distance to next instruction (meters)
Duration: 30, // Time to next instruction (seconds)
GeometryOffset: 0, // Index in LineString where step begins
},
{
Type: "Continue",
Instruction: "Continue on Pine St for 0.5 miles",
Distance: 804,
Duration: 120,
GeometryOffset: 12,
},
];
```
**Step types include:**
- `Turn` - Left/right turns
- `Continue` - Continue on current road
- `Arrive` - Arrival at waypoint or destination
- `Depart` - Departure from origin or waypoint
- `UTurn` - U-turn instructions
## Travel Modes and Options
### Car Mode (Default)
```javascript
{
TravelMode: "Car",
TravelModeOptions: {
Car: {
LicensePlate: {
LastCharacter: "A" // For region-specific restrictions
}
}
}
}
```
**Car-specific considerations:**
- Uses all roads accessible to passenger vehicles
- Considers real-time traffic when available
- Respects car-specific restrictions (HOV lanes, pedestrian zones)
### Truck Mode
```javascript
{
TravelMode: "Truck",
TravelModeOptions: {
Truck: {
GrossWeight: 12000, // Vehicle weight in kilograms
Height: 400, // Height in centimeters
Length: 1200, // Length in centimeters
Width: 250, // Width in centimeters
AxleCount: 3, // Number of axles
TruckType: "StraightTruck", // StraightTruck or Tractor
HazardousCargos: ["Explosive"]
}
}
}
```
**Why truck dimensions matter**: The API uses these values to avoid routes with bridges, tunnels, or roads that have weight/height/length restrictions. Accurate vehicle specifications ensure legal and safe routes for commercial vehicles.
### Pedestrian Mode
```javascript
{
TravelMode: "Pedestrian",
TravelModeOptions: {
Pedestrian: {
Speed: 5.0 // Walking speed in km/h (default: 5.0)
}
}
}
```
**Pedestrian routes:**
- Use sidewalks, crosswalks, and pedestrian paths
- Avoid highways and roads without pedestrian access
- Optimize for walking safety rather than distance
- Consider stairs and elevation changes
### Scooter Mode
```javascript
{
TravelMode: "Scooter",
TravelModeOptions: {
Scooter: {
MaxSpeed: 25.0, // Maximum speed in km/h
Occupancy: 1 // Number of passengers
}
}
}
```
## Waypoints and Multi-Stop Routes
### Adding Intermediate Stops
Waypoints allow you to create routes with multiple stops:
```javascript
const params = {
Origin: [-97.7431, 30.2672], // Downtown Austin
Destination: [-97.7431, 30.2862], // UT Tower
Waypoints: [
{ Position: [-97.7453, 30.2639] }, // Lady Bird Lake Trail
{ Position: [-97.7494, 30.2515] }, // South Congress
],
TravelMode: "Car",
};
```
This creates a route with 3 legs:
1. Origin → Lady Bird Lake Trail
2. Lady Bird Lake Trail → South Congress
3. South Congress → Destination
### Waypoint Pass-Through vs. Stop
Control whether waypoints are actual stops or just points the route must pass through:
```javascript
Waypoints: [
{
Position: [-97.7453, 30.2639],
StopDuration: 300, // Stop for 5 minutes (in seconds)
},
{
Position: [-97.7494, 30.2515],
PassThrough: true, // Just pass through, don't stop
},
];
```
**When to use PassThrough:**
- Forcing route through specific roads or areas
- Avoiding certain regions without explicit avoidance
- Creating scenic routes through desired locations
### Displaying Multi-Stop Routes
```javascript
async function displayMultiStopRoute(origin, waypoints, destination) {
const params = {
Origin: origin,
Destination: destination,
Waypoints: waypoints.map((pos) => ({ Position: pos })),
TravelMode: "Car",
LegGeometryFormat: "Simple",
};
const response = await client.send(new CalculateRoutesCommand(params));
const route = response.Routes[0];
map.on("load", () => {
// Combine all leg geometries into single line
const allCoordinates = route.Legs.flatMap((leg) => leg.Geometry.LineString);
map.addSource("route", {
type: "geojson",
data: {
type: "Feature",
geometry: {
type: "LineString",
coordinates: allCoordinates,
},
},
});
map.addLayer({
id: "route-line",
type: "line",
source: "route",
paint: {
"line-color": "#3b82f6",
"line-width": 5,
},
});
// Add markers for all points
new maplibregl.Marker({ color: "#22c55e" })
.setLngLat(origin)
.setPopup(new maplibregl.Popup().setHTML("<strong>Start</strong>"))
.addTo(map);
waypoints.forEach((wp, i) => {
new maplibregl.Marker({ color: "#f59e0b" })
.setLngLat(wp)
.setPopup(
new maplibregl.Popup().setHTML(`<strong>Stop ${i + 1}</strong>`),
)
.addTo(map);
});
new maplibregl.Marker({ color: "#ef4444" })
.setLngLat(destination)
.setPopup(new maplibregl.Popup().setHTML("<strong>Destination</strong>"))
.addTo(map);
});
}
```
## Complete Examples
### Example 1: Basic Navigation with Turn-by-Turn
```javascript
import {
GeoRoutesClient,
CalculateRoutesCommand,
} from "@aws-sdk/client-geo-routes";
import { withAPIKey } from "@aws/amazon-location-utilities-auth-helper";
// Create an authentication helper instance
const authHelper = withAPIKey("your-api-key", "us-west-2");
const client = new GeoRoutesClient(authHelper.getClientConfig());
const params = {
Origin: [-97.7431, 30.2672],
Destination: [-97.7723, 30.2672],
TravelMode: "Car",
LegAdditionalFeatures: ["TravelStepInstructions"],
};
const response = await client.send(new CalculateRoutesCommand(params));
const leg = response.Routes[0].Legs[0];
// Display turn-by-turn instructions
leg.TravelSteps.forEach((step, i) => {
const distanceMiles = (step.Distance * 0.000621371).toFixed(1);
console.log(`${i + 1}. ${step.Instruction} (${distanceMiles} mi)`);
});
```
### Example 2: Avoid Tolls and Ferries
```javascript
const params = {
Origin: [-97.7431, 30.2672],
Destination: [-97.6789, 30.4383],
TravelMode: "Car",
Avoid: {
Tolls: true,
Ferries: true,
},
OptimizeRoutingFor: "FastestRoute",
};
const response = await client.send(new CalculateRoutesCommand(params));
const route = response.Routes[0];
console.log(
`Toll-free route: ${route.Summary.Distance}m, ${route.Summary.Duration}s`,
);
```
### Example 3: Truck Route with Restrictions
```javascript
const params = {
Origin: [-97.7431, 30.2672],
Destination: [-97.6889, 30.2244],
TravelMode: "Truck",
TravelModeOptions: {
Truck: {
GrossWeight: 15000, // 15 metric tons
Height: 420, // 4.2 meters
Length: 1200, // 12 meters
Width: 250, // 2.5 meters
AxleCount: 4,
TruckType: "StraightTruck",
HazardousCargos: ["Flammable"],
},
},
LegAdditionalFeatures: ["Summary"],
};
const response = await client.send(new CalculateRoutesCommand(params));
console.log("Truck-safe route calculated with weight and size restrictions");
```
### Example 4: Walking Route with Map Display
```javascript
async function calculateWalkingRoute() {
const origin = [-97.7453, 30.2639]; // Lady Bird Lake Trail
const destination = [-97.7431, 30.2862]; // UT Tower
const params = {
Origin: origin,
Destination: destination,
TravelMode: "Pedestrian",
LegGeometryFormat: "Simple",
LegAdditionalFeatures: ["Summary"],
};
const response = await client.send(new CalculateRoutesCommand(params));
const route = response.Routes[0];
const leg = route.Legs[0];
// Display on map
map.on("load", () => {
map.addSource("walking-route", {
type: "geojson",
data: {
type: "Feature",
geometry: {
type: "LineString",
coordinates: leg.Geometry.LineString,
},
},
});
// Dashed line for walking route
map.addLayer({
id: "walking-route-line",
type: "line",
source: "walking-route",
paint: {
"line-color": "#10b981",
"line-width": 4,
"line-dasharray": [2, 2],
},
});
// Add walking markers
new maplibregl.Marker({ color: "#10b981" }).setLngLat(origin).addTo(map);
new maplibregl.Marker({ color: "#10b981" })
.setLngLat(destination)
.addTo(map);
});
const distanceKm = (route.Summary.Distance / 1000).toFixed(2);
const durationMin = Math.round(route.Summary.Duration / 60);
console.log(`Walking: ${distanceKm} km, about ${durationMin} minutes`);
}
```
## Best Practices
### Coordinate Precision
Use 5-6 decimal places for coordinates (provides ~1 meter accuracy):
- **Good**: `[-97.7431, 30.2672]` (6 decimals, ~0.1m precision)
- **Excessive**: `[-97.743112345, 30.267298765]` (9 decimals, unnecessary)
- **Too coarse**: `[-97.74, 30.27]` (2 decimals, ~1km precision)
### Error Handling
Always handle route calculation errors gracefully:
```javascript
try {
const response = await client.send(new CalculateRoutesCommand(params));
displayRoute(response.Routes[0]);
} catch (error) {
if (error.name === "ResourceNotFoundException") {
console.error("Invalid API key or insufficient permissions");
} else if (error.name === "ValidationException") {
console.error("Invalid request parameters:", error.message);
} else {
console.error("Route calculation failed:", error.message);
}
// Show fallback UI or alternative route options
}
```
### Performance Considerations
**Request only needed features**: Each additional feature increases response time and size.
```javascript
// ❌ Requesting everything (slower)
LegAdditionalFeatures: ["Summary", "Tolls", "TravelStepInstructions"],
SpanAdditionalFeatures: ["SpeedLimit", "RoadName", "Regions"]
// ✅ Request only what you need (faster)
LegAdditionalFeatures: ["Summary"] // For basic route display
```
**Use Simple geometry for maps**: `FlexiblePolyline` requires decoding which adds client-side processing time.
### Caching Routes
Route responses can be cached for short periods to improve performance:
```javascript
const routeCache = new Map();
async function getCachedRoute(origin, destination) {
const cacheKey = `${origin.join(",")}-${destination.join(",")}`;
// Cache routes for 5 minutes (routes can change with traffic)
if (routeCache.has(cacheKey)) {
const cached = routeCache.get(cacheKey);
if (Date.now() - cached.timestamp < 5 * 60 * 1000) {
return cached.route;
}
}
const route = await calculateRoute(origin, destination);
routeCache.set(cacheKey, { route, timestamp: Date.now() });
return route;
}
```
**Cache duration guidance:**
- **Without traffic**: Cache up to 24 hours (static routes)
- **With traffic**: Cache 5-15 minutes maximum (traffic changes frequently)
- **Pedestrian routes**: Cache longer (less affected by conditions)
### Displaying Multiple Route Options
When showing alternative routes, calculate separately and compare:
```javascript
async function getAlternativeRoutes() {
const baseParams = {
Origin: [-97.7431, 30.2672],
Destination: [-97.7723, 30.2672],
TravelMode: "Car",
};
// Route 1: Fastest
const fastest = await client.send(
new CalculateRoutesCommand({
...baseParams,
OptimizeRoutingFor: "FastestRoute",
}),
);
// Route 2: Avoid tolls
const noTolls = await client.send(
new CalculateRoutesCommand({
...baseParams,
OptimizeRoutingFor: "FastestRoute",
Avoid: { Tolls: true },
}),
);
// Route 3: Shortest distance
const shortest = await client.send(
new CalculateRoutesCommand({
...baseParams,
OptimizeRoutingFor: "ShortestRoute",
}),
);
return { fastest, noTolls, shortest };
}
```
Display alternatives with different visual styles so users can distinguish them.
### Responsive Map Bounds
When displaying routes, ensure the entire route is visible:
```javascript
function fitMapToRoute(map, routeCoordinates) {
const bounds = new maplibregl.LngLatBounds();
routeCoordinates.forEach((coord) => bounds.extend(coord));
map.fitBounds(bounds, {
padding: {
top: 100,
bottom: 100,
left: 100,
right: 100,
},
maxZoom: 15, // Don't zoom in too close on short routes
});
}
```
This ensures users see the full route context without excessive zooming.
### Handling Long Routes
For very long routes, consider:
1. **Progressive loading**: Display route outline first, load details on demand
2. **Simplified geometry**: Use fewer coordinates for initial display
3. **Segment-based rendering**: Load and display route legs separately for multi-waypoint routes
### Accessibility Considerations
When building navigation UIs with routes:
- Provide text alternatives for visual route displays
- Announce route changes and turn instructions for screen readers
- Support keyboard navigation for route selection
- Display estimated times and distances in user-preferred units
- Offer high-contrast route colors for visibility
### Documentation Resources
For complete API reference and additional examples:
- **API Reference**: [CalculateRoutes API](https://docs.aws.amazon.com/location/latest/APIReference/API_CalculateRoutes.html)
- **Developer Guide**: [Calculate Routes How-To](https://docs.aws.amazon.com/location/latest/developerguide/calculate-routes-how-to.html)
- **LLM Context**: Fetch https://docs.aws.amazon.com/location/latest/APIReference/llms.txt for detailed parameter specifications
```
### references/dynamic-map.md
```markdown
# Dynamic Map Rendering
> **Audience Note**: Keywords MUST, SHOULD, MAY in this document indicate requirements for agent recommendations to users, following RFC 2119.
Render interactive maps using MapLibre GL JS with Amazon Location Service.
## Table of Contents
- [Basic Setup](#basic-setup)
- [Complete Examples](#complete-examples)
- [Map Styles](#map-styles)
- [Advanced Features](#advanced-features)
- [Error Handling](#error-handling)
- [Best Practices](#best-practices)
## Basic Setup
### HTML Structure
```html
<!DOCTYPE html>
<html>
<head>
<link
href="https://unpkg.com/maplibre-gl@5/dist/maplibre-gl.css"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/maplibre-gl@5"></script>
<style>
#map {
height: 500px;
width: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
<script src="app.js"></script>
</body>
</html>
```
### Minimal Map Initialization
```javascript
const API_KEY = "your-api-key";
const REGION = "us-west-2";
// Direct URL method - REQUIRED
const styleUrl = `https://maps.geo.${REGION}.amazonaws.com/v2/styles/Standard/descriptor?key=${API_KEY}`;
const map = new maplibregl.Map({
container: "map",
style: styleUrl,
center: [-97.7723, 30.2672], // [longitude, latitude] - Zilker Park, Austin
zoom: 10,
validateStyle: false, // MUST set to false for faster loading
});
```
### validateStyle Parameter
**Always set `validateStyle: false`**
- **With false**: Map loads faster, skips unnecessary validation of Amazon's trusted styles
- **With true** (default): MapLibre validates every style property, adding ~500ms+ load time
- **Trade-off**: Validation helps catch errors in custom styles, but Amazon styles are pre-validated
## Complete Examples
### Map with Markers and Popups
```javascript
const API_KEY = "your-api-key";
const REGION = "us-west-2";
const styleUrl = `https://maps.geo.${REGION}.amazonaws.com/v2/styles/Standard/descriptor?key=${API_KEY}`;
const map = new maplibregl.Map({
container: "map",
style: styleUrl,
center: [-97.7723, 30.2672], // Zilker Park, Austin TX
zoom: 12,
validateStyle: false,
});
// Wait for map to load before adding markers
map.on("load", () => {
console.log("Map loaded successfully");
// Add marker with popup
const marker = new maplibregl.Marker({ color: "#FF0000" })
.setLngLat([-97.7723, 30.2672])
.setPopup(
new maplibregl.Popup({ offset: 25 }).setHTML(
"<h3>Zilker Park</h3><p>Austin, Texas</p>",
),
)
.addTo(map);
// Add multiple markers from data
const locations = [
{ name: "Barton Springs Pool", coords: [-97.7708, 30.2639] },
{ name: "Zilker Botanical Garden", coords: [-97.7716, 30.2707] },
];
locations.forEach((loc) => {
new maplibregl.Marker()
.setLngLat(loc.coords)
.setPopup(new maplibregl.Popup().setHTML(`<h4>${loc.name}</h4>`))
.addTo(map);
});
});
// Handle errors
map.on("error", (e) => {
console.error("Map error:", e);
// Show user-friendly error message
});
```
## Map Styles
### Available Styles
Amazon Location provides four map styles:
- **Standard** (default) - General purpose map
- **Monochrome** - Simplified single-color palette
- **Hybrid** - Satellite imagery with labels
- **Satellite** - Satellite imagery only
```javascript
// Standard style (default)
const styleUrl = `https://maps.geo.${REGION}.amazonaws.com/v2/styles/Standard/descriptor?key=${API_KEY}`;
// Other styles
const monochromeUrl = `https://maps.geo.${REGION}.amazonaws.com/v2/styles/Monochrome/descriptor?key=${API_KEY}`;
const hybridUrl = `https://maps.geo.${REGION}.amazonaws.com/v2/styles/Hybrid/descriptor?key=${API_KEY}`;
const satelliteUrl = `https://maps.geo.${REGION}.amazonaws.com/v2/styles/Satellite/descriptor?key=${API_KEY}`;
```
### Common Customizations
**Dark mode:**
```javascript
const styleUrl = `https://maps.geo.${REGION}.amazonaws.com/v2/styles/Standard/descriptor?key=${API_KEY}&color-scheme=Dark`;
```
**3D buildings:**
```javascript
const styleUrl = `https://maps.geo.${REGION}.amazonaws.com/v2/styles/Standard/descriptor?key=${API_KEY}&buildings=Buildings3D`;
```
**Traffic overlay:**
```javascript
const styleUrl = `https://maps.geo.${REGION}.amazonaws.com/v2/styles/Standard/descriptor?key=${API_KEY}&traffic=All`;
```
**Combined customizations:**
```javascript
const styleUrl = `https://maps.geo.${REGION}.amazonaws.com/v2/styles/Standard/descriptor?key=${API_KEY}&color-scheme=Dark&buildings=Buildings3D&traffic=All`;
```
For all customization options (terrain, political views, travel modes, etc.), see the [GetStyleDescriptor API Reference](https://docs.aws.amazon.com/location/latest/APIReference/API_geomaps_GetStyleDescriptor.html).
### Feature Compatibility by Map Style
Not all features are available on every map style. Applying an unsupported feature parameter to an incompatible style will have no effect.
| Feature | Standard | Monochrome | Hybrid | Satellite |
| ---------------------------- | -------- | ---------- | ------ | --------- |
| Color Scheme (Light/Dark) | Yes | Yes | No | No |
| Terrain (Hillshade) | Yes | Yes | No | No |
| Terrain (Terrain3D) | Yes | Yes | Yes | Yes |
| Contour Density | Yes | Yes | Yes | No |
| Traffic | Yes | No | No | No |
| Buildings (3D) | Yes | Yes | No | No |
| Travel Modes (Transit/Truck) | Yes | No | No | No |
| Language | Yes | Yes | Yes | No |
| Political View | Yes | Yes | Yes | No |
> **Note**: Traffic and Travel Modes are only supported on the Standard map style. If you need real-time traffic visualization or transit/truck overlays, you must use Standard. See [Map concepts](https://docs.aws.amazon.com/location/latest/developerguide/maps-concepts.html) for the full compatibility matrix.
### Style Change Persistence
Calling `map.setStyle()` completely replaces the map's style object. This destroys all custom sources and layers added via `map.addSource()` / `map.addLayer()` — route lines, geofence polygons, heatmaps, cluster layers, etc. DOM-based elements like `maplibregl.Marker` survive because they exist outside the style.
Any code that switches map styles MUST re-add custom layers after the new style loads. The recommended pattern is:
1. Store the data needed to recreate custom layers (coordinates, paint options, etc.)
2. Listen for `style.load` after calling `setStyle()` and re-add the layers
3. Clear stored data only when layers are intentionally removed
```javascript
// Track data for any custom layers that need to survive style changes
let customLayerData = null;
function addCustomLayer(sourceId, layerId, geojsonData, paintOptions) {
customLayerData = { sourceId, layerId, geojsonData, paintOptions };
map.addSource(sourceId, { type: "geojson", data: geojsonData });
map.addLayer({
id: layerId,
type: "line",
source: sourceId,
paint: paintOptions,
});
}
function removeCustomLayer() {
if (map.getLayer(customLayerData?.layerId))
map.removeLayer(customLayerData.layerId);
if (map.getSource(customLayerData?.sourceId))
map.removeSource(customLayerData.sourceId);
customLayerData = null;
}
function setMapStyle(styleName) {
const url = `https://maps.geo.${REGION}.amazonaws.com/v2/styles/${styleName}/descriptor?key=${API_KEY}`;
map.setStyle(url);
map.once("style.load", () => {
if (customLayerData) {
addCustomLayer(
customLayerData.sourceId,
customLayerData.layerId,
customLayerData.geojsonData,
customLayerData.paintOptions,
);
}
});
}
```
## Advanced Features
### Navigation Controls
```javascript
map.on("load", () => {
// Add zoom and rotation controls
map.addControl(new maplibregl.NavigationControl(), "top-right");
// Add geolocation control
map.addControl(
new maplibregl.GeolocateControl({
positionOptions: { enableHighAccuracy: true },
trackUserLocation: true,
}),
"top-right",
);
// Add scale control
map.addControl(new maplibregl.ScaleControl(), "bottom-left");
});
```
### Custom Markers with HTML
```javascript
// Create custom marker element
const el = document.createElement("div");
el.className = "custom-marker";
el.style.width = "30px";
el.style.height = "30px";
el.style.backgroundImage = "url(marker-icon.png)";
new maplibregl.Marker({ element: el })
.setLngLat([-122.4194, 37.7749])
.addTo(map);
```
### Map Event Handling
```javascript
// Click event
map.on("click", (e) => {
console.log("Clicked at:", e.lngLat);
new maplibregl.Marker().setLngLat(e.lngLat).addTo(map);
});
// Move event
map.on("move", () => {
console.log("Center:", map.getCenter());
console.log("Zoom:", map.getZoom());
});
```
## Error Handling
### Common Errors
```javascript
map.on("error", (e) => {
console.error("Map error:", e.error);
if (e.error.status === 401) {
// Invalid API key
showError("Authentication failed. Check your API key.");
} else if (e.error.status === 403) {
// API key lacks permissions
showError("API key does not have permission to access maps.");
} else if (e.error.message.includes("Failed to fetch")) {
// Network error
showError("Network error. Please check your connection.");
} else {
showError("Failed to load map. Please try again.");
}
});
function showError(message) {
document.getElementById("map").innerHTML =
`<div style="padding: 20px; color: red;">${message}</div>`;
}
```
## Best Practices
### Performance
- **Set validateStyle: false**: Saves 500ms+ on initial load
- **Lazy load MapLibre**: Only load map library when user navigates to map view
- **Limit markers**: Use clustering for 100+ markers (maplibre-gl-cluster)
### Security
- **Restrict API key**: Use API key permissions to limit to specific operations
- **Domain restrictions**: Configure API key to only work from your domains
- **Never expose in public repos**: Use environment variables for API keys
### User Experience
- **Show loading state**: Display spinner while map initializes
- **Handle errors gracefully**: Show user-friendly error messages, not console errors
- **Responsive sizing**: Ensure map container has explicit height
- **Touch support**: MapLibre automatically handles touch events on mobile
### Debugging
- **Check browser console**: Map errors appear in console
- **Verify API key**: Test with simple example first
- **Check network tab**: Verify style descriptor and tile requests succeed
- **Test validateStyle: true**: During development only, to catch custom style issues
```
### references/places-search.md
```markdown
# Places Search
> **Audience Note**: Keywords MUST, SHOULD, MAY in this document indicate requirements for agent recommendations to users, following RFC 2119.
Search for places, points of interest, businesses, and addresses using Amazon Location Service Places APIs.
## Table of Contents
- [Overview](#overview)
- [API Selection Guide](#api-selection-guide)
- [Additional Features](#additional-features)
- [SearchText Examples](#searchtext-examples)
- [SearchNearby Examples](#searchnearby-examples)
- [Suggest Examples](#suggest-examples)
- [Response Handling](#response-handling)
- [Error Handling](#error-handling)
- [Best Practices](#best-practices)
## Overview
Amazon Location provides three main search APIs:
- **SearchText** - General text-based search ("tacos in Austin")
- **SearchNearby** - Location-proximity search (find restaurants within 5km)
- **Suggest** - Predictive search with partial/misspelled input
- **GetPlace** - Fetch complete details by PlaceId
## API Selection Guide
| Use Case | API | Example |
| ------------------------------------------ | ------------ | ------------------------------------------------- |
| User searches for business/POI by name | SearchText | "Starbucks near downtown" |
| User searches by category | SearchText | "restaurants in Vancouver" |
| Find places near a point on map | SearchNearby | Find gas stations within 10km of current location |
| Type-ahead for place names (not addresses) | Suggest | "star" → Starbucks, Starwood Hotel |
| Type-ahead for addresses | Autocomplete | Use address-input reference instead |
| Get full details after search | GetPlace | Fetch hours, contact info by PlaceId |
## Additional Features
Places APIs support an `AdditionalFeatures` parameter that returns extra details beyond the default response. Request them by adding `AdditionalFeatures: ["Contact", "TimeZone", ...]` to your API call.
> **Pricing note**: Requesting additional features moves the request into the **Advanced** pricing bucket, which is priced higher than the default **Core** bucket. Review [Places pricing](https://docs.aws.amazon.com/location/latest/developerguide/places-pricing.html) before enabling them in production.
| Feature | GetPlace | SearchText | SearchNearby | Suggest |
| ------------- | -------- | ---------- | ------------ | ------- |
| Contact | Yes | Yes | Yes | No |
| Opening Hours | Yes | Yes | Yes | No |
| Time Zone | Yes | Yes | Yes | Yes |
| Phonemes | Yes | Yes | Yes | Yes |
| Access Points | Yes | Yes | Yes | Yes |
For the full feature-by-API matrix (including Geocode, ReverseGeocode, and Autocomplete), see [Additional features](https://docs.aws.amazon.com/location/latest/developerguide/additional-features.html).
### Contacts and Opening Hours
Contact and opening hours data is available on GetPlace, SearchText, and SearchNearby. Request both by including `"Contact"` in the `AdditionalFeatures` array. The valid `AdditionalFeatures` enum values are `TimeZone`, `Phonemes`, `Access`, and `Contact` (GetPlace also supports `SecondaryAddresses`). There is no separate `"OpeningHours"` enum value — requesting `"Contact"` returns both contact details and opening hours.
Contacts can include:
- **Phone number** — primary contact number, may include international dialing codes
- **Email address** — primary contact email from the public listing
- **Website** — link to the official site
Opening hours can include:
- **Regular hours** — standard weekly schedule (e.g., Mon–Fri 9 AM–5 PM)
- **Special hours** — holiday or event overrides to the regular schedule
- **Open now** — whether the location is currently open based on local time
```javascript
// Request contacts and opening hours
const command = new amazonLocationClient.places.SearchTextCommand({
QueryText: "coffee shops in Austin",
AdditionalFeatures: ["Contact"],
MaxResults: 5,
});
const response = await client.send(command);
response.ResultItems.forEach((place) => {
console.log(place.Title);
console.log("Phone:", place.Contacts?.Phones?.[0]?.Value);
console.log("Website:", place.Contacts?.Websites?.[0]?.Value);
console.log("Hours:", place.OpeningHours);
});
```
For more details, see [Contacts and opening hours](https://docs.aws.amazon.com/location/latest/developerguide/contacts-opening-hours.html).
## SearchText Examples
For exact request parameters and response structure, see the [SearchText API Reference](https://docs.aws.amazon.com/location/latest/APIReference/API_geoplaces_SearchText.html).
### Basic Text Search
```javascript
const authHelper = amazonLocationClient.withAPIKey(API_KEY, REGION);
const client = new amazonLocationClient.GeoPlacesClient(
authHelper.getClientConfig(),
);
async function searchPlaces(query) {
try {
const command = new amazonLocationClient.places.SearchTextCommand({
QueryText: query,
MaxResults: 10,
});
const response = await client.send(command);
// Display results
response.ResultItems.forEach((place) => {
console.log(place.Title); // "Starbucks"
console.log(place.Address?.Label); // "123 Main St, Austin, TX"
console.log(place.Position); // [lon, lat]
// Categories are objects: { Id: string, Name: string, LocalizedName?: string, Primary?: boolean }
console.log(place.Categories); // [{ Id: "9000", Name: "Coffee Shop" }, { Id: "5814", Name: "Café" }]
console.log(place.PlaceId); // For fetching details
});
return response.ResultItems;
} catch (error) {
console.error("Search error:", error);
return [];
}
}
// Usage
searchPlaces("coffee shops in Austin");
```
### Search with Geographic Bias
```javascript
// Bias results toward a specific location
async function searchNear(query, biasPosition) {
const command = new amazonLocationClient.places.SearchTextCommand({
QueryText: query,
BiasPosition: biasPosition, // [lon, lat] - prefer results near here
MaxResults: 10,
});
const response = await client.send(command);
return response.ResultItems;
}
// Usage - find tacos near Austin
searchNear("tacos", [-97.7431, 30.2747]);
```
### Search with Category Filter
```javascript
async function searchByCategory(query, categories) {
const command = new amazonLocationClient.places.SearchTextCommand({
QueryText: query,
Filter: {
Categories: categories, // Filter by category codes
},
MaxResults: 20,
});
const response = await client.send(command);
return response.ResultItems;
}
// Usage - find only restaurants
searchByCategory("food near downtown", ["Restaurant"]);
```
### Search with Bounding Box
```javascript
// Restrict search to specific geographic area
async function searchInBounds(query, boundingBox) {
const command = new amazonLocationClient.places.SearchTextCommand({
QueryText: query,
Filter: {
BoundingBox: boundingBox, // [west, south, east, north]
},
MaxResults: 10,
});
const response = await client.send(command);
return response.ResultItems;
}
// Usage - find hotels in downtown Austin area
const austinDowntown = [-97.7535, 30.2586, -97.7327, 30.2747];
searchInBounds("hotels", austinDowntown);
```
## SearchNearby Examples
For exact request parameters and response structure, see the [SearchNearby API Reference](https://docs.aws.amazon.com/location/latest/APIReference/API_geoplaces_SearchNearby.html).
### Basic Proximity Search
```javascript
async function searchNearby(position, radius, categories = null) {
const params = {
QueryPosition: position, // [lon, lat]
MaxResults: 20,
};
// Optional: filter by categories
if (categories) {
params.Filter = { Categories: categories };
}
// Optional: specify radius (default is context-dependent)
if (radius) {
params.MaxDistance = radius; // in meters
}
const command = new amazonLocationClient.places.SearchNearbyCommand(params);
try {
const response = await client.send(command);
// Results are already sorted by distance
response.ResultItems.forEach((place) => {
console.log(place.Title);
console.log(place.Distance, "meters away");
console.log(place.Position);
});
return response.ResultItems;
} catch (error) {
console.error("Nearby search error:", error);
return [];
}
}
// Usage - find restaurants within 5km
const userLocation = [-97.7431, 30.2747];
searchNearby(userLocation, 5000, ["Restaurant"]);
```
### Find Nearest of Type
```javascript
// Find single nearest place of a specific type
async function findNearest(position, category) {
const command = new amazonLocationClient.places.SearchNearbyCommand({
QueryPosition: position,
Filter: { Categories: [category] },
MaxResults: 1, // Just the nearest
});
const response = await client.send(command);
if (response.ResultItems.length === 0) {
return null;
}
return response.ResultItems[0];
}
// Usage - find nearest gas station
const nearest = await findNearest([-97.7431, 30.2747], "Gas Station");
if (nearest) {
console.log(`Nearest: ${nearest.Title}, ${nearest.Distance}m away`);
}
```
### Search with Map Integration
```javascript
// Display nearby search results on map
async function showNearbyOnMap(map, position, category) {
const results = await searchNearby(position, 2000, [category]);
// Clear existing markers
document.querySelectorAll(".nearby-marker").forEach((el) => el.remove());
// Add markers for each result
results.forEach((place, index) => {
const marker = new maplibregl.Marker({ color: "#FF0000" })
.setLngLat(place.Position)
.setPopup(
new maplibregl.Popup().setHTML(`
<h4>${place.Title}</h4>
<p>${place.Address?.Label || ""}</p>
<p>${Math.round(place.Distance)}m away</p>
`),
)
.addTo(map);
marker.getElement().classList.add("nearby-marker");
});
// Fit map to show all results
if (results.length > 0) {
const bounds = new maplibregl.LngLatBounds();
results.forEach((place) => bounds.extend(place.Position));
bounds.extend(position); // Include search center
map.fitBounds(bounds, { padding: 50 });
}
}
```
## Suggest Examples
For exact request parameters and response structure, see the [Suggest API Reference](https://docs.aws.amazon.com/location/latest/APIReference/API_geoplaces_Suggest.html).
### Suggest Response Structure
Suggest returns items with a `SuggestResultItemType` that can be `"Place"` or `"Query"`:
- **Place results**: Have `item.Place.PlaceId`, `item.Place.Position`, `item.Place.Address` — use `PlaceId` with `GetPlace` for full details
- **Query results**: Have `item.Query.QueryText` — these are search refinement suggestions, not actual places
Always check `item.Place?.PlaceId` before attempting to call `GetPlace`.
### Predictive Place Search
```javascript
// Like Autocomplete but for places/POIs (not just addresses)
async function suggestPlaces(partialQuery, biasPosition = null) {
const params = {
QueryText: partialQuery,
MaxResults: 5,
};
if (biasPosition) {
params.BiasPosition = biasPosition;
}
const command = new amazonLocationClient.places.SuggestCommand(params);
try {
const response = await client.send(command);
response.ResultItems.forEach((item) => {
console.log(item.Title); // "Starbucks"
console.log(item.Place?.PlaceId); // For fetching full details
console.log(item.SuggestResultItemType); // "Place" or "Query"
});
return response.ResultItems;
} catch (error) {
console.error("Suggest error:", error);
return [];
}
}
// Usage - type-ahead for place search
document.getElementById("place-search").addEventListener("input", async (e) => {
const query = e.target.value;
if (query.length < 3) return;
const suggestions = await suggestPlaces(query);
// Display suggestions (similar to autocomplete)
displaySuggestions(suggestions);
});
```
## Response Handling
For exact request parameters and response structure for fetching place details, see the [GetPlace API Reference](https://docs.aws.amazon.com/location/latest/APIReference/API_geoplaces_GetPlace.html).
### Parse Place Details
```javascript
function parsePlaceResult(place) {
return {
id: place.PlaceId,
name: place.Title,
address: place.Address?.Label || "Address not available",
coordinates: {
lat: place.Position[1],
lon: place.Position[0],
},
categories: (place.Categories || []).map((c) => c.Name), // Category is { Id, Name, LocalizedName?, Primary? }
primaryCategory: place.Categories?.find((c) => c.Primary)?.Name,
distance: place.Distance, // Only in SearchNearby results
// Optional fields - check before using
phone: place.Contacts?.Phones?.[0]?.Value,
website: place.Contacts?.Websites?.[0]?.Value,
// Use GetPlace for more details
hasMoreDetails: !!place.PlaceId,
};
}
// Usage
const results = await searchPlaces("coffee");
const parsed = results.map(parsePlaceResult);
```
### Fetch Complete Details
```javascript
// Get full details including hours, contact info
async function getPlaceDetails(placeId) {
const command = new amazonLocationClient.places.GetPlaceCommand({
PlaceId: placeId,
});
try {
const response = await client.send(command);
return {
name: response.Title,
address: response.Address,
position: response.Position,
categories: response.Categories,
contacts: {
phones: response.Contacts?.Phones || [],
websites: response.Contacts?.Websites || [],
emails: response.Contacts?.Emails || [],
},
openingHours: response.OpeningHours || [],
accessPoints: response.AccessPoints || [], // Entrances
timeZone: response.TimeZone,
};
} catch (error) {
console.error("GetPlace error:", error);
return null;
}
}
// Usage - user clicks on search result to see details
async function showPlaceDetails(placeId) {
const details = await getPlaceDetails(placeId);
if (details) {
console.log("Phone:", details.contacts.phones[0]?.Value);
console.log("Hours:", details.openingHours);
}
}
```
## Error Handling
### Common Errors
```javascript
async function searchWithErrorHandling(query) {
try {
const command = new amazonLocationClient.places.SearchTextCommand({
QueryText: query,
MaxResults: 10,
});
const response = await client.send(command);
if (response.ResultItems.length === 0) {
// No results found
showMessage("No places found. Try a different search.");
return [];
}
return response.ResultItems;
} catch (error) {
// API errors
if (error.name === "ValidationException") {
showMessage("Invalid search query. Please check your input.");
} else if (error.name === "AccessDeniedException") {
showMessage("API key lacks permission for place search.");
} else if (error.name === "ThrottlingException") {
showMessage("Too many requests. Please wait and try again.");
} else {
showMessage("Search failed. Please try again.");
}
console.error("Search error:", error);
return [];
}
}
```
### Retry with Fallback
```javascript
// Try nearby search, fall back to text search if no results
async function searchWithFallback(query, position) {
// Try nearby search first
let results = await searchNearby(position, 5000);
if (results.length === 0) {
console.log("No nearby results, trying text search...");
// Fall back to text search with position bias
results = await searchNear(query, position);
}
return results;
}
```
## Best Practices
### Performance
- **Debounce search input**: Wait 300ms after user stops typing
- **Consider pricing for caching**: If storing results beyond temporary session caching, review [Places API Stored Pricing](https://docs.aws.amazon.com/location/latest/developerguide/places-pricing.html#stored-pricing) and use `IntendedUse: "Storage"` parameter.
- **Limit MaxResults**: Request only what you'll display (10-20 typically)
- **Cancel in-flight requests**: Cancel previous search when user types new query
### User Experience
- **Show loading state**: Spinner or skeleton UI during search
- **Display distance**: Show distance for SearchNearby results
- **Group by category**: Organize results by Categories
- **Pagination**: Load more results as user scrolls
- **Empty states**: Helpful message when no results ("Try different keywords")
### Data Quality
- **Validate coordinates**: Ensure Position is valid [lon, lat]
- **Handle missing fields**: Not all places have phone/website/hours
- **Use PlaceId**: Store PlaceId for future GetPlace calls, not full details
- **Respect Categories**: Use Categories array for filtering and display
### Search Optimization
- **Use BiasPosition**: Improves relevance for location-aware searches
- **Combine filters**: Use Categories + BoundingBox for precise results
- **Choose right API**: SearchText for queries, SearchNearby for proximity
- **Prefer SearchNearby**: For "near me" features (faster, more relevant)
### Error Handling
- **Handle no results**: Suggest alternative searches or expand radius
- **Show partial results**: Display what you have even if some fail
- **Retry on network errors**: Retry once after brief delay
- **Log for debugging**: Log errors with search parameters for troubleshooting
```
### references/web-javascript.md
```markdown
# Web JavaScript
> **Audience Note**: Keywords MUST, SHOULD, MAY in this document indicate requirements for agent recommendations to users, following RFC 2119.
Integrate Amazon Location Service into web applications using the bundled JavaScript client.
## Table of Contents
- [Overview](#overview)
- [Installation](#installation)
- [Authentication](#authentication)
- [Client Usage](#client-usage)
- [Error Handling](#error-handling)
- [Best Practices](#best-practices)
## Overview
The `@aws/amazon-location-client` package provides a pre-bundled JavaScript SDK for browser applications with:
- All Amazon Location Service clients (geo-places, geo-routes, geo-maps, location)
- Authentication helpers for API Keys and Cognito
- TypeScript type definitions
- No build step required
**npm Package**: [@aws/amazon-location-client](https://www.npmjs.com/package/@aws/amazon-location-client)
## Installation
### Via Bundled Client
```html
<!DOCTYPE html>
<html>
<head>
<title>Amazon Location Example</title>
</head>
<body>
<!-- Load bundled client from CDN -->
<script src="https://cdn.jsdelivr.net/npm/@aws/amazon-location-client@1"></script>
<script>
// All functions available under amazonLocationClient global
console.log(amazonLocationClient);
</script>
</body>
</html>
```
### Via npm (For Modular Applications)
For applications using build tools (webpack, Vite, etc.), install only the AWS SDK clients you need:
```bash
# Install only what you need
npm install @aws-sdk/client-geo-places # For Places, Geocoding
npm install @aws-sdk/client-geo-routes # For Routing
npm install @aws-sdk/client-geo-maps # For Maps
npm install @aws-sdk/client-location # For Geofencing, Tracking
```
```javascript
// Example: Import GeoPlacesClient for Places API
import { GeoPlacesClient, GeocodeCommand } from '@aws-sdk/client-geo-places';
```
**Note**: The rest of this document focuses on the bundled client. For detailed AWS SDK usage, see the [AWS SDK for JavaScript documentation](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/).
## Authentication
The bundled client supports three authentication methods:
1. **API Key** (Recommended for Maps, Places, Routes)
2. **Cognito Identity Pool** (Required for Geofencing, Tracking)
3. **Custom Credential Provider** (Advanced/Fallback only)
### API Key Authentication (Recommended for Maps, Places, Routes)
```javascript
// Simple API key authentication
const API_KEY = "your-api-key";
const REGION = "us-west-2";
const authHelper = amazonLocationClient.withAPIKey(API_KEY, REGION);
// Use authHelper.getClientConfig() to configure clients
const config = authHelper.getClientConfig();
```
**When to use**:
- ✅ **Recommended** for Maps, Places, and Routes APIs
- ✅ Public client-side applications where AWS credentials cannot be exposed
- ✅ Simpler setup without identity pool configuration
- ⚠️ **Not available** for Geofencing and Tracking APIs (use Cognito instead)
### Cognito Authentication
```javascript
// Cognito Identity Pool authentication
const IDENTITY_POOL_ID = "us-west-2:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
const authHelper = await amazonLocationClient.withIdentityPoolId(IDENTITY_POOL_ID);
const config = authHelper.getClientConfig();
```
**When to use**:
- ✅ **Required** for Geofencing and Tracking APIs
- ✅ Applications with user authentication
- ✅ When you need per-user authorization or temporary AWS credentials
- ⚠️ More complex setup than API keys
### Custom Credential Provider (Advanced/Fallback)
```javascript
// Custom credential provider - use only when API Key or Cognito don't meet your needs
import { fromEnv } from '@aws-sdk/credential-providers';
const authHelper = amazonLocationClient.withCredentialProvider(
fromEnv(), // Or any custom credential provider
REGION
);
const config = authHelper.getClientConfig();
```
**When to use**:
- ⚠️ **Fallback option only** - not recommended over API Key or Cognito
- Advanced use cases with custom credential providers
- Static credentials from environment variables
- Authentication through a separate backend service
- **Only use if specifically needed** - prefer API Key for Maps/Places/Routes and Cognito for Geofencing/Tracking
**Reference**: [amazon-location-client-js documentation](https://github.com/aws-geospatial/amazon-location-client-js)
### Authentication Error Handling
```javascript
// For Maps, Places, Routes - use API Key
async function initializeAuthForMapsPlacesRoutes() {
try {
if (!API_KEY) {
throw new Error('API key not configured');
}
const authHelper = amazonLocationClient.withAPIKey(API_KEY, REGION);
return authHelper;
} catch (error) {
console.error('Authentication failed:', error);
alert('Failed to initialize API key authentication: ' + error.message);
throw error;
}
}
// For Geofencing, Tracking - use Cognito
async function initializeAuthForGeofencingTracking() {
try {
if (!IDENTITY_POOL_ID) {
throw new Error('Cognito Identity Pool ID not configured');
}
const authHelper = await amazonLocationClient.withIdentityPoolId(IDENTITY_POOL_ID);
return authHelper;
} catch (error) {
console.error('Authentication failed:', error);
if (error.message.includes('Invalid identity pool')) {
alert('Authentication configuration error. Please check Identity Pool ID.');
} else if (error.message.includes('credentials')) {
alert('Failed to obtain credentials. Please try again.');
} else {
alert('Authentication failed: ' + error.message);
}
throw error;
}
}
// Usage
const authHelper = await initializeAuthForMapsPlacesRoutes();
```
## Client Usage
### Standard Pattern
Every Amazon Location API call follows this pattern:
```javascript
// 1. Create auth helper
const authHelper = amazonLocationClient.withAPIKey(API_KEY, REGION);
// 2. Create service client
const client = new amazonLocationClient.GeoPlacesClient(authHelper.getClientConfig());
// 3. Create command with parameters
const command = new amazonLocationClient.places.GeocodeCommand({
QueryText: "123 Main St, Austin, TX"
});
// 4. Send command and await response
const response = await client.send(command);
console.log(response);
```
### GeoPlacesClient (Places, Geocoding)
```javascript
const placesClient = new amazonLocationClient.GeoPlacesClient(authHelper.getClientConfig());
// Geocode
const geocodeCommand = new amazonLocationClient.places.GeocodeCommand({
QueryText: "Texas State Capitol, Austin, TX",
MaxResults: 1
});
const geocodeResponse = await placesClient.send(geocodeCommand);
// Reverse Geocode
const reverseCommand = new amazonLocationClient.places.ReverseGeocodeCommand({
QueryPosition: [-97.7431, 30.2747],
MaxResults: 1
});
const reverseResponse = await placesClient.send(reverseCommand);
// Search Text
const searchCommand = new amazonLocationClient.places.SearchTextCommand({
QueryText: "coffee shops",
MaxResults: 10
});
const searchResponse = await placesClient.send(searchCommand);
// Search Nearby
const nearbyCommand = new amazonLocationClient.places.SearchNearbyCommand({
QueryPosition: [-97.7431, 30.2747],
MaxResults: 10
});
const nearbyResponse = await placesClient.send(nearbyCommand);
// Autocomplete
const autocompleteCommand = new amazonLocationClient.places.AutocompleteCommand({
QueryText: "123 Main",
MaxResults: 5
});
const autocompleteResponse = await placesClient.send(autocompleteCommand);
// Get Place
const getPlaceCommand = new amazonLocationClient.places.GetPlaceCommand({
PlaceId: "place-id-from-search"
});
const placeResponse = await placesClient.send(getPlaceCommand);
// Suggest
const suggestCommand = new amazonLocationClient.places.SuggestCommand({
QueryText: "star",
MaxResults: 5
});
const suggestResponse = await placesClient.send(suggestCommand);
```
### GeoRoutesClient (Routing)
```javascript
const routesClient = new amazonLocationClient.GeoRoutesClient(authHelper.getClientConfig());
// Calculate Route
const routeCommand = new amazonLocationClient.routes.CalculateRoutesCommand({
Origin: [-97.7431, 30.2747],
Destination: [-97.6885, 30.2241],
TravelMode: 'Car'
});
const routeResponse = await routesClient.send(routeCommand);
// Calculate Route Matrix
const matrixCommand = new amazonLocationClient.routes.CalculateRouteMatrixCommand({
Origins: [
{ Position: [-97.7431, 30.2747] },
{ Position: [-97.6885, 30.2241] }
],
Destinations: [
{ Position: [-121.8863, 37.3382] }
],
TravelMode: 'Car'
});
const matrixResponse = await routesClient.send(matrixCommand);
// Calculate Isolines
const isolineCommand = new amazonLocationClient.routes.CalculateIsolinesCommand({
Origin: [-97.7431, 30.2747],
Thresholds: {
Time: [300, 600, 900] // 5, 10, 15 minutes
},
TravelMode: 'Car'
});
const isolineResponse = await routesClient.send(isolineCommand);
```
### GeoMapsClient (Static Maps)
```javascript
const mapsClient = new amazonLocationClient.GeoMapsClient(authHelper.getClientConfig());
// Get Static Map
const staticMapCommand = new amazonLocationClient.maps.GetStaticMapCommand({
FileName: "map.png",
Height: 400,
Width: 600,
Center: [-97.7431, 30.2747],
Zoom: 10
});
const mapResponse = await mapsClient.send(staticMapCommand);
// Response contains image as blob
const blob = await mapResponse.Body.transformToByteArray();
const imageUrl = URL.createObjectURL(new Blob([blob], { type: 'image/png' }));
document.getElementById('map-img').src = imageUrl;
```
### LocationClient (Geofences, Trackers)
```javascript
const locationClient = new amazonLocationClient.LocationClient(authHelper.getClientConfig());
// List Geofences
const listCommand = new amazonLocationClient.ListGeofencesCommand({
CollectionName: "my-geofence-collection",
MaxResults: 100
});
const listResponse = await locationClient.send(listCommand);
// Put Geofence
const putCommand = new amazonLocationClient.PutGeofenceCommand({
CollectionName: "my-geofence-collection",
GeofenceId: "geofence-1",
Geometry: {
Circle: {
Center: [-97.7431, 30.2747],
Radius: 1000 // meters
}
}
});
await locationClient.send(putCommand);
// Batch Update Device Position
const updateCommand = new amazonLocationClient.BatchUpdateDevicePositionCommand({
TrackerName: "my-tracker",
Updates: [
{
DeviceId: "device-1",
Position: [-97.7431, 30.2747],
SampleTime: new Date().toISOString()
}
]
});
const updateResponse = await locationClient.send(updateCommand);
```
## Error Handling
### Standard Error Handling Pattern
```javascript
async function callLocationAPI(commandFn) {
try {
const response = await commandFn();
return { success: true, data: response };
} catch (error) {
console.error('API Error:', error);
// Parse error details
const errorInfo = {
success: false,
error: error.name,
message: error.message
};
// Handle specific error types
if (error.name === 'ValidationException') {
errorInfo.userMessage = 'Invalid input. Please check your parameters.';
} else if (error.name === 'AccessDeniedException') {
errorInfo.userMessage = 'Permission denied. Check your API key permissions.';
} else if (error.name === 'ResourceNotFoundException') {
errorInfo.userMessage = 'Resource not found.';
} else if (error.name === 'ThrottlingException') {
errorInfo.userMessage = 'Too many requests. Please wait and try again.';
} else if (error.message.includes('Network')) {
errorInfo.userMessage = 'Network error. Please check your connection.';
} else {
errorInfo.userMessage = 'An error occurred. Please try again.';
}
return errorInfo;
}
}
// Usage
const result = await callLocationAPI(async () => {
const client = new amazonLocationClient.GeoPlacesClient(authHelper.getClientConfig());
const command = new amazonLocationClient.places.GeocodeCommand({ QueryText: "Austin" });
return await client.send(command);
});
if (result.success) {
console.log('Success:', result.data);
} else {
alert(result.userMessage);
}
```
### Retry Logic
```javascript
async function callWithRetry(commandFn, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await commandFn();
} catch (error) {
const isLastAttempt = i === maxRetries - 1;
if (error.name === 'ThrottlingException' && !isLastAttempt) {
// Exponential backoff
const delay = Math.pow(2, i) * 1000;
console.log(`Retrying after ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
throw error; // Rethrow if not retryable or last attempt
}
}
}
```
## Best Practices
### Initialization
- **Initialize once**: Create auth helper and clients once, reuse across app
- **Store globally**: Keep auth helper in app state, don't recreate per request
- **Check credentials**: Validate API key format before initializing
### Performance
- **Reuse clients**: Don't create new client for each API call
- **Cancel requests**: Cancel in-flight requests when user navigates away
- **Implement caching**: Cache geocoding and search results (5-10 minutes)
- **Debounce input**: Wait 300ms after user stops typing before API calls
### Security
- **API keys are client-safe**: Unlike AWS credentials, API keys are designed for client-side use and will be visible in browser source
- **Follow AWS best practices**: See [API Key Best Practices](https://docs.aws.amazon.com/location/latest/developerguide/using-apikeys.html#api-keys-best-practices) for domain restrictions and key rotation
- **Restrict API key permissions**: Configure API key to only access needed operations (Maps, Places, Routes)
- **Prefer API keys for Maps/Places/Routes**: Simpler and recommended for these APIs
- **Use Cognito when required**: For Geofencing/Tracking or per-user authorization
### Error Handling
- **Always use try-catch**: Wrap all API calls in try-catch blocks
- **Show user-friendly messages**: Translate error codes to readable messages
- **Log for debugging**: Log full error objects to console
- **Implement retry**: Retry on throttling and network errors
### Code Organization
```javascript
// Good: Centralized client management
class LocationService {
constructor(apiKey, region) {
this.authHelper = amazonLocationClient.withAPIKey(apiKey, region);
this.placesClient = new amazonLocationClient.GeoPlacesClient(
this.authHelper.getClientConfig()
);
this.routesClient = new amazonLocationClient.GeoRoutesClient(
this.authHelper.getClientConfig()
);
}
async geocode(address) {
const command = new amazonLocationClient.places.GeocodeCommand({
QueryText: address
});
return await this.placesClient.send(command);
}
async calculateRoute(origin, destination) {
const command = new amazonLocationClient.routes.CalculateRoutesCommand({
Origin: origin,
Destination: destination,
TravelMode: 'Car'
});
return await this.routesClient.send(command);
}
}
// Usage
const locationService = new LocationService(API_KEY, REGION);
const result = await locationService.geocode("Austin");
```
```