The project uses Place Index for geocoding, without requiring Map tiles.
Place Index: TravelGuidePlaceIndex (Esri)
Main APIs:
SearchPlaceIndexForPosition (reverse geocoding)SearchPlaceIndexForText (forward geocoding)AWS Console Steps:
TravelGuidePlaceIndexSingleUseSAM Template:
TravelGuidePlaceIndex:
Type: AWS::Location::PlaceIndex
Properties:
IndexName: TravelGuidePlaceIndex
DataSource: Esri
PricingPlan: RequestBasedUsage
DataSourceConfiguration:
IntendedUse: SingleUse
In functions/utils/location_service.py:
Logic flow:
Functions:
CreateArticleFunctionUpdateArticleFunctionEnvironment Variables:
Environment:
Variables:
PLACE_INDEX_NAME: !Ref TravelGuidePlaceIndex
LOCATION_CACHE_TABLE: !Ref LocationCacheTable
USE_AWS_LOCATION: 'true'
import boto3
import json
from datetime import datetime, timedelta
location_client = boto3.client('location')
dynamodb = boto3.resource('dynamodb')
cache_table = dynamodb.Table(os.environ['LOCATION_CACHE_TABLE'])
PLACE_INDEX = os.environ['PLACE_INDEX_NAME']
def reverse_geocode(latitude, longitude):
"""Convert coordinates to place name"""
# Generate cache key
cache_key = f"reverse:{latitude:.6f},{longitude:.6f}"
# Check cache
try:
response = cache_table.get_item(Key={'cacheKey': cache_key})
if 'Item' in response:
cached_data = response['Item']
# Check if not expired
if datetime.now().timestamp() < cached_data['expiresAt']:
print(f"Cache HIT: {cache_key}")
return json.loads(cached_data['data'])
except Exception as e:
print(f"Cache read error: {e}")
print(f"Cache MISS: {cache_key}")
# Call AWS Location Service
try:
response = location_client.search_place_index_for_position(
IndexName=PLACE_INDEX,
Position=[longitude, latitude], # Note: [lng, lat]
MaxResults=1
)
if response['Results']:
place = response['Results'][0]['Place']
result = {
'label': place.get('Label', ''),
'municipality': place.get('Municipality', ''),
'country': place.get('Country', ''),
'region': place.get('Region', '')
}
# Save to cache
save_to_cache(cache_key, result)
return result
except Exception as e:
print(f"AWS Location error: {e}")
# Fallback to Nominatim
return fallback_nominatim(latitude, longitude)
return None
def forward_geocode(text):
"""Convert place name to coordinates"""
cache_key = f"forward:{text}"
# Check cache (similar to reverse)
# ...
# Call AWS Location Service
try:
response = location_client.search_place_index_for_text(
IndexName=PLACE_INDEX,
Text=text,
MaxResults=5
)
results = []
for item in response['Results']:
place = item['Place']
results.append({
'label': place.get('Label', ''),
'position': place['Geometry']['Point'], # [lng, lat]
'country': place.get('Country', '')
})
# Save to cache
save_to_cache(cache_key, results)
return results
except Exception as e:
print(f"AWS Location error: {e}")
return []
def save_to_cache(key, data, ttl_days=30):
"""Save geocoding result to DynamoDB cache"""
try:
expires_at = int((datetime.now() + timedelta(days=ttl_days)).timestamp())
cache_table.put_item(
Item={
'cacheKey': key,
'data': json.dumps(data),
'expiresAt': expires_at,
'createdAt': int(datetime.now().timestamp())
}
)
print(f"Saved to cache: {key}")
except Exception as e:
print(f"Cache write error: {e}")
def fallback_nominatim(latitude, longitude):
"""Fallback to Nominatim if AWS Location fails"""
import requests
try:
url = f"https://nominatim.openstreetmap.org/reverse"
params = {
'lat': latitude,
'lon': longitude,
'format': 'json'
}
headers = {'User-Agent': 'TravelGuideApp/1.0'}
response = requests.get(url, params=params, headers=headers, timeout=5)
if response.status_code == 200:
data = response.json()
return {
'label': data.get('display_name', ''),
'municipality': data.get('address', {}).get('city', ''),
'country': data.get('address', {}).get('country', ''),
'region': data.get('address', {}).get('state', '')
}
except Exception as e:
print(f"Nominatim fallback error: {e}")
return None

Table Schema:
LocationCacheTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: LocationCache
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: cacheKey
AttributeType: S
KeySchema:
- AttributeName: cacheKey
KeyType: HASH
TimeToLiveSpecification:
Enabled: true
AttributeName: expiresAt
Attributes:
cacheKey (String, Primary Key): reverse:lat,lng or forward:textdata (String): JSON-encoded geocoding resultexpiresAt (Number): Unix timestamp for TTLcreatedAt (Number): Creation timestampBenefits:
Minimum IAM policy for Lambda:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"geo:SearchPlaceIndexForPosition",
"geo:SearchPlaceIndexForText"
],
"Resource": "arn:aws:geo:ap-southeast-1:*:place-index/TravelGuidePlaceIndex"
},
{
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:Query"
],
"Resource": "arn:aws:dynamodb:ap-southeast-1:*:table/LocationCache"
}
]
}
SAM Policy Templates:
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref LocationCacheTable
- Statement:
- Effect: Allow
Action:
- geo:SearchPlaceIndexForPosition
- geo:SearchPlaceIndexForText
Resource: !GetAtt TravelGuidePlaceIndex.Arn
Metrics to track:
CloudWatch Custom Metrics:
import boto3
cloudwatch = boto3.client('cloudwatch')
def log_cache_metric(metric_name, value):
cloudwatch.put_metric_data(
Namespace='TravelGuide/Location',
MetricData=[
{
'MetricName': metric_name,
'Value': value,
'Unit': 'Count'
}
]
)
# Usage
log_cache_metric('CacheHit', 1)
log_cache_metric('CacheMiss', 1)
log_cache_metric('LocationServiceCall', 1)
Increase Cache TTL
Cache Popular Queries
Batch Requests (if applicable)
Monitor Usage
Fallback Strategy
If you want to use AWS Location Maps (instead of OSM/Esri public tiles), you have two approaches:
import L from 'leaflet';
import { Signer } from '@aws-amplify/core';
// With API Key
const map = L.map('map').setView([10.8231, 106.6297], 13);
L.tileLayer(
'https://maps.geo.ap-southeast-1.amazonaws.com/maps/v0/maps/{mapName}/tiles/{z}/{x}/{y}?key={apiKey}',
{
attribution: '© Amazon Location Service',
mapName: 'TravelGuideMap',
apiKey: 'YOUR_API_KEY'
}
).addTo(map);
// With Cognito (SigV4)
// More complex - requires signing each tile request
// See AWS documentation for full implementation
Causes:
Solution:
Causes:
Solution:
Causes:
Solution:
Causes:
Solution:
Always Use Cache
Implement Fallback
Validate Input
Monitor Usage
Optimize TTL
Log Cache Performance