Using Tangram ES for iOS with Amazon Location Service
Tangram ES
Tangram styles built to work with the Tilezen
schema
-
Bubble Wrap
– A full-featured wayfinding style with helpful icons for points of interest -
Cinnabar
– A classic look and go-to for general mapping applications -
Refill
– A minimalist map style designed for data visualization overlays, inspired by the seminal Toner style by Stamen Design -
Tron
– An exploration of scale transformations in the visual language of TRON -
Walkabout
– An outdoor-focused style that's perfect for hiking or getting out and about
This guide describes how to integrate Tangram ES for iOS with Amazon Location using
the Tangram style called Cinnabar. This sample is available as part of the
Amazon Location Service samples repository on GitHub
While other Tangram styles are best accompanied by raster tiles, which encode terrain information, this feature isn't yet supported by Amazon Location.
Important
The Tangram styles in the following tutorial are only compatible with
Amazon Location map resources configured with the VectorHereContrast
style.
Building the application: Initialization
To initialize the application:
-
Create a new Xcode project from the App template.
-
Select SwiftUI for its interface.
-
Select SwiftUI application for its Life Cycle.
-
Select Swift for its language.
Building the application: Add dependencies
To add dependencies, you can use a dependency manager, such as CocoaPods
-
In your terminal, install CocoaPods:
sudo gem install cocoapods
-
Navigate to your application's project directory and initialize the Podfile with the CocoaPods package manager:
pod init
-
Open the Podfile to add
AWSCore
andTangram-es
as dependencies:platform :ios, '12.0' target 'Amazon Location Service Demo' do use_frameworks! pod 'AWSCore' pod 'Tangram-es' end
-
Download and install dependencies:
pod install --repo-update
-
Open the Xcode workspace that CocoaPods created:
xed .
Building the application: Configuration
Add the following keys and values to Info.plist to configure the application and disable telemetry:
Key | Value |
---|---|
AWSRegion | us-east-1 |
IdentityPoolId | us-east-1:54f2ba88-9390-498d-aaa5-0d97fb7ca3bd |
MapName | ExampleMap |
SceneURL | https://www.nextzen.org/carto/cinnabar-style/9/cinnabar-style.zip |
Building the application: ContentView layout
To render the map, edit ContentView.swift
:
-
Add a
MapView
which renders the map. -
Add a
TextField
which displays attribution.
This also sets the map's initial center point.
Note
You must provide word mark or text attribution for each data provider
that you use, either on your application or your documentation.
Attribution strings are included in the style descriptor response under
the sources.esri.attribution
, sources.here.attribution
,
and source.grabmaptiles.attribution
keys. When using Amazon Location
resources with data
providers, make sure to read the service terms and
conditions
import SwiftUI import TangramMap struct ContentView: View { var body: some View { MapView() .cameraPosition(TGCameraPosition( center: CLLocationCoordinate2DMake(49.2819, -123.1187), zoom: 10, bearing: 0, pitch: 0)) .edgesIgnoringSafeArea(.all) .overlay( Text("© 2020 HERE") .disabled(true) .font(.system(size: 12, weight: .light, design: .default)) .foregroundColor(.black) .background(Color.init(Color.RGBColorSpace.sRGB, white: 0.5, opacity: 0.5)) .cornerRadius(1), alignment: .bottomTrailing) } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
Building the application: Request transformation
Create a new Swift file named AWSSignatureV4URLHandler.swift
containing the following class definition to intercept AWS requests and
sign them using Signature Version
4. This will be registered as a URL handler within the Tangram
MapView
.
import AWSCore import TangramMap class AWSSignatureV4URLHandler: TGDefaultURLHandler { private let region: AWSRegionType private let identityPoolId: String private let credentialsProvider: AWSCredentialsProvider init(region: AWSRegionType, identityPoolId: String) { self.region = region self.identityPoolId = identityPoolId self.credentialsProvider = AWSCognitoCredentialsProvider(regionType: region, identityPoolId: identityPoolId) super.init() } override func downloadRequestAsync(_ url: URL, completionHandler: @escaping TGDownloadCompletionHandler) -> UInt { if url.host?.contains("amazonaws.com") != true { // not an AWS URL return super.downloadRequestAsync(url, completionHandler: completionHandler) } // URL-encode spaces, etc. let keyPath = String(url.path.dropFirst()) guard let keyPathSafe = keyPath.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else { print("Invalid characters in path '\(keyPath)'; unsafe to sign") return super.downloadRequestAsync(url, completionHandler: completionHandler) } // sign the URL let endpoint = AWSEndpoint(region: region, serviceName: "geo", url: url) let requestHeaders: [String: String] = ["host": endpoint!.hostName] let task = AWSSignatureV4Signer .generateQueryStringForSignatureV4( withCredentialProvider: credentialsProvider, httpMethod: .GET, expireDuration: 60, endpoint: endpoint!, keyPath: keyPathSafe, requestHeaders: requestHeaders, requestParameters: .none, signBody: true) task.waitUntilFinished() if let error = task.error as NSError? { print("Error occurred: \(error)") } if let result = task.result { // have Tangram fetch the signed URL return super.downloadRequestAsync(result as URL, completionHandler: completionHandler) } // fall back to an unsigned URL return super.downloadRequestAsync(url, completionHandler: completionHandler) } }
Building the application: Map view
The map view is responsible for initializing an instance of
AWSSignatureV4Delegate
and configuring the underlying
MGLMapView
, which fetches resources and renders the map. It
also handles propagating attribution strings from the style descriptor's
source back to the ContentView
.
Create a new Swift file named MapView.swift
containing the
following struct
definition:
import AWSCore import TangramMap import SwiftUI struct MapView: UIViewRepresentable { private let mapView: TGMapView init() { let regionName = Bundle.main.object(forInfoDictionaryKey: "AWSRegion") as! String let identityPoolId = Bundle.main.object(forInfoDictionaryKey: "IdentityPoolId") as! String let mapName = Bundle.main.object(forInfoDictionaryKey: "MapName") as! String let sceneURL = URL(string: Bundle.main.object(forInfoDictionaryKey: "SceneURL") as! String)! let region = (regionName as NSString).aws_regionTypeValue() // rewrite tile URLs to point at AWS resources let sceneUpdates = [ TGSceneUpdate(path: "sources.mapzen.url", value: "https://maps.geo.\(regionName).amazonaws.com/maps/v0/maps/\(mapName)/tiles/{z}/{x}/{y}")] // instantiate a TGURLHandler that will sign AWS requests let urlHandler = AWSSignatureV4URLHandler(region: region, identityPoolId: identityPoolId) // instantiate the map view and attach the URL handler mapView = TGMapView(frame: .zero, urlHandler: urlHandler) // load the map style and apply scene updates (properties modified at runtime) mapView.loadScene(from: sceneURL, with: sceneUpdates) } func cameraPosition(_ cameraPosition: TGCameraPosition) -> MapView { mapView.cameraPosition = cameraPosition return self } // MARK: - UIViewRepresentable protocol func makeUIView(context: Context) -> TGMapView { return mapView } func updateUIView(_ uiView: TGMapView, context: Context) { } }
Running this application displays a full-screen map in the style of your
choosing. This sample is available as part of the Amazon Location Service samples
repository on GitHub