

# Using Amazon Location Maps in your application
<a name="using-maps"></a>

**Note**  
We released a new version of the Maps API, see the updated [Maps Developer Guide](https://docs.aws.amazon.com//location/latest/developerguide/maps.html) or [Maps API](https://docs.aws.amazon.com//location/latest/APIReference/API_Operations_Amazon_Location_Service_Maps_V2.html) for revised information.

Amazon Location maps are cost-effective and interactive. You can replace an existing map in your application to save money, or add a new one to display location-based data visually, such as your store location.

![\[\]](http://docs.aws.amazon.com/location/previous/developerguide/images/maps_park_example.png)


Amazon Location Service lets you choose a data provider for map operations by creating and configuring a map resource. The map resource configures the data provider and the style that is used to render the map.

After you create your resource, you can send requests by using the AWS SDK directly, or by using a library made specifically for rendering maps in your environment. 

**Note**  
For an overview of map concepts, see [Learn about Maps resources in Amazon Location Service](map-concepts.md).

**Topics**
+ [

# Prerequisites for using Amazon Location maps
](map-prerequisites.md)
+ [

# Display a map in your application with Amazon Location
](display-map.md)
+ [

# Drawing data features on a map
](drawing-on-a-map.md)
+ [

# Setting extents for a map using MapLibre
](setting-map-extents.md)
+ [

# Managing your map resources with Amazon Location
](managing-maps.md)

# Prerequisites for using Amazon Location maps
<a name="map-prerequisites"></a>

Before you start utilizing the mapping capabilities of Amazon Location Service, there are a few prerequisites that need to be fulfilled. This page outlines the necessary steps and requirements to ensure a smooth integration of interactive maps into your applications. 

## Create a map resource
<a name="create-map-resource"></a>

To use a map in your application you must have a map resource, which specifies the map style and data provider to use in your maps.

**Note**  
If your application is tracking or routing assets you use in your business, such as delivery vehicles or employees, you must not use Esri as your geolocation provider. See section 82 of the [AWS service terms](https://aws.amazon.com/service-terms) for more details.

You can create a map resource using the Amazon Location Service console, the AWS CLI, or the Amazon Location APIs.

------
#### [ Console ]

**To create a map resource using the Amazon Location Service console**

1. In the Amazon Location console, on the [https://console.aws.amazon.com/location/maps/home](https://console.aws.amazon.com/location/maps/home) page, choose **Create map** to preview map styles.

1. Add a name and description for the new map resource.

1. Choose a map style.
**Note**  
If your application is tracking or routing assets you use in your business, such as delivery vehicles or employees, you must not use Esri as your geolocation provider. See section 82 of the [AWS service terms](https://aws.amazon.com/service-terms) for more details.

1. Choose from the [Political views](map-concepts.md#political-views) to use.

1. Agree to the **Amazon Location Terms and Conditions**, then choose **Create map**. You can interact with the map that you've chosen: zoom in, zoom out, or pan in any direction.

1. To allow your users to switch styles (for example, to allow them to switch between satellite imagery and vector style), you must create a map resource for each style.

   You can delete resources with map styles that you don’t want to use on the [Maps home page](https://console.aws.amazon.com/location/maps/home) in the console. 

------
#### [ API ]

**To create a map resource using the Amazon Location APIs**

Use the `[CreateMap](https://docs.aws.amazon.com/location/previous/APIReference/API_CreateMap.html)` operation from the Amazon Location APIs.

The following example is an API request to create a map resource called *ExampleMap* using the *VectorEsriStreets* map style. 

```
POST /maps/v0/maps HTTP/1.1
Content-type: application/json

{
   "Configuration": { 
      "Style": "VectorEsriStreets"
   },
   "MapName": "ExampleMap"
   }
}
```

**Note**  
If your application is tracking or routing assets you use in your business, such as delivery vehicles or employees, you must not use Esri as your geolocation provider. See section 82 of the [AWS service terms](https://aws.amazon.com/service-terms) for more details.

------
#### [ AWS CLI ]

**To create a map resource using AWS CLI commands**

Use the `[create-map](https://docs.aws.amazon.com/cli/latest/reference/location/create-map.html)` command.

The following example creates a map resource called *ExampleMap* using *VectorEsriStreets* as the map style. 

```
aws location \
  create-map \
  --configuration Style="VectorEsriStreets" \
  --map-name "ExampleMap"
```

**Note**  
If your application is tracking or routing assets you use in your business, such as delivery vehicles or employees, you must not use Esri as your geolocation provider. See section 82 of the [AWS service terms](https://aws.amazon.com/service-terms) for more details.

------

## Authenticating your requests
<a name="using-maps-set-up-authentication"></a>

Once you create a map resource and you're ready to begin building location features into your application, you need to choose how you would authenticate your requests.

**Note**  
Most maps front end applications require unauthenticated access to the maps or other features of Amazon Location Service. Depending on your application, you might want to use AWS Signature v4 to authenticate requests, or you can use Amazon Cognito or Amazon Location API keys for unauthenticated use. To learn more about all of these options, see [Grant access to Amazon Location Service](how-to-access.md).

# Display a map in your application with Amazon Location
<a name="display-map"></a>

This section provides tutorials on how to use map rendering tools to display a map in your mobile or web application when using Amazon Location APIs. As mentioned in the [How to use Amazon Location Service](using-amazon-location.md) topic, you have a choice of libraries to use when rendering maps with Amazon Location, including Amplify, MapLibre, and Tangram.

Do one of the following to display a map in your application:
+ The most direct way to display a map in your web and mobile front end applications is to use MapLibre. You can follow the [MapLibre tutorials](tutorial-maplibre.md) or even the [Quick start tutorial](getting-started.md) to learn how to use MapLibre.
+ If you are an existing AWS Amplify developer, you may want to use the Amplify Geo SDK. To learn more, follow the [ Amplify tutorial](https://docs.aws.amazon.com/location/previous/developerguide/tutorial-map-amplify.html).
+ If you are an existing user of Tangram, and want to continue to use it to render your map, while moving to Amazon Location Service, follow the [Tangram tutorial](tutorial-tangram-js.md).

**Topics**
+ [

# Using the MapLibre library with Amazon Location Service
](tutorial-maplibre.md)
+ [

# Using the Amplify library with Amazon Location Service
](tutorial-map-amplify.md)
+ [

# Tutorial: using Tangram with Amazon Location Service
](tutorial-tangram.md)

# Using the MapLibre library with Amazon Location Service
<a name="tutorial-maplibre"></a>

The following tutorials walk you through using the MapLibre Library with Amazon Location. 

**Topics**
+ [

# Using MapLibre GL JS with Amazon Location Service
](tutorial-maplibre-gl-js.md)
+ [

# Using the MapLibre Native SDK for Android with Amazon Location Service
](tutorial-maplibre-android.md)
+ [

# Using the MapLibre Native SDK for iOS with Amazon Location Service
](tutorial-maplibre-ios.md)

# Using MapLibre GL JS with Amazon Location Service
<a name="tutorial-maplibre-gl-js"></a>

Use [MapLibre GL JS](https://github.com/maplibre/maplibre-gl-js) to embed client-side maps into web applications.

MapLibre GL JS is an open-source JavaScript library that's compatible with the styles and tiles provided by the Amazon Location Service Maps API. You can integrate MapLibre GL JS within a basic HTML or JavaScript application to embed customizable and responsive client-side maps.

This tutorial describes how to integrate MapLibre GL JS with Amazon Location within a basic HTML and JavaScript application. The same libraries and techniques presented in this tutorial also apply to frameworks, such as [React](https://reactjs.org/) and [Angular](https://angular.io/). 

The sample application for this tutorial is available as part of the Amazon Location Service samples repository on [GitHub](https://github.com/aws-samples/amazon-location-samples).

## Building the application: Scaffolding
<a name="tutorial-maplibre-js-scaffolding"></a>

This tutorial creates a web application that uses JavaScript to build a map on an HTML page.

Begin by creating an HTML page (`index.html`) that includes the map's container:
+ Enter a `div` element with an `id` of `map` to apply the map's dimensions to the map view. The dimensions are inherited from the viewport.

```
<html>
  <head>
    <style>
      body {
        margin: 0;
      }
 
      #map {
        height: 100vh; /* 100% of viewport height */
      }
    </style>
  </head>
  <body>
    <!-- map container -->
    <div id="map" />
  </body>
</html>
```

## Building the application: Adding dependencies
<a name="tutorial-maplibre-js-add-dependencies"></a>

Add the following dependencies to your application: 
+ MapLibre GL JS (v3.x), and its associated CSS.
+ The Amazon Location [JavaScript Authentication helper](loc-sdk-auth.md).

```
<!-- CSS dependencies -->
<link
  href="https://unpkg.com/maplibre-gl@3.x/dist/maplibre-gl.css"
  rel="stylesheet"
/>
<!-- JavaScript dependencies -->
<script src="https://unpkg.com/maplibre-gl@3.x/dist/maplibre-gl.js"></script>
<script src="https://unpkg.com/@aws/amazon-location-authentication-helper.js"></script>
<script>
  // application-specific code
</script>
```

This creates an empty page with the map's container. 

## Building the application: Configuration
<a name="tutorial-maplibre-js-configuration"></a>

To configure your application using JavaScript:

1. Enter the names and identifiers of your resources.

   ```
   // Cognito Identity Pool ID
   const identityPoolId = "us-east-1:54f2ba88-9390-498d-aaa5-0d97fb7ca3bd";
   // Amazon Location Service Map name
   const mapName = "ExampleMap";
   ```

1. Instantiate a credential provider using the unauthenticated identity pool you created in [Using maps - Step 2, Set up authentication](map-prerequisites.md#using-maps-set-up-authentication). We will put this in a function called `initializeMap`, that will also contain other map initialization code, added in the next step

   ```
   // extract the Region from the Identity Pool ID; this will be used for both Amazon Cognito and Amazon Location
   AWS.config.region = identityPoolId.split(":")[0];
    
   async function initializeMap() {
     // Create an authentication helper instance using credentials from Cognito
     const authHelper = await amazonLocationAuthHelper.withIdentityPoolId(identityPoolId);
   
     // ... more here, later
   }
   ```

## Building the application: Map initialization
<a name="tutorial-maplibre-js-request-map-init"></a>

For the map to display after the page is loaded, you must initialize the map. You can adjust the initial map location, add additional controls, and overlay data.

```
async function initializeMap() {
  // Create an authentication helper instance using credentials from Cognito
  const authHelper = await amazonLocationAuthHelper.withIdentityPoolId(identityPoolId);
 
  // Initialize the map
  const map = new maplibregl.Map({
    container: "map",
    center: [-123.1187, 49.2819], // initial map centerpoint
    zoom: 10, // initial map zoom
    style: `https://maps.geo.${region}.amazonaws.com/maps/v0/maps/${mapName}/style-descriptor`,
    ...authHelper.getMapAuthenticationOptions(), // authentication, using cognito
  });
 
  map.addControl(new maplibregl.NavigationControl(), "top-left");
}
 
initializeMap();
```

**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 `sources.grabmaptiles.attribution` keys. MapLibre GL JS will automatically provide attribution. When using Amazon Location resources with [data providers](https://docs.aws.amazon.com/location/previous/developerguide/what-is-data-provider.html), make sure to read the [service terms and conditions](https://aws.amazon.com/service-terms/).

## Running the application
<a name="tutorial-maplibre-js-writing-js"></a>

You can run this sample application by using it in a local web server, or opening it in a browser.

To use a local web server, you can use npx, because it's installed as part of Node.js. You can use `npx serve` from within the same directory as `index.html`. This serves the application on `localhost:5000`.

**Note**  
If the policy you created for your unauthenticated Amazon Cognito role includes a `referer` condition, you might be blocked from testing with `localhost:` URLs. In this case. you can test with a web server that provides a URL that is in your policy.

After completing the tutorial, the final application looks like the following example.

```
<!-- index.html -->
<html>
  <head>
    <link href="https://unpkg.com/maplibre-gl@3.x/dist/maplibre-gl.css" rel="stylesheet" />
    <style>
      body {
        margin: 0;
      }
      #map {
        height: 100vh;
      }
    </style>
  </head>

  <body>
    <!-- map container -->
    <div id="map" />
    <!-- JavaScript dependencies -->
    <script src="https://unpkg.com/maplibre-gl@3.x/dist/maplibre-gl.js"></script>
    <script src="https://unpkg.com/@aws/amazon-location-authentication-helper.js"></script>
    <script>
      // configuration
      const identityPoolId = "us-east-1:54f2ba88-9390-498d-aaa5-0d97fb7ca3bd"; // Cognito Identity Pool ID
      const mapName = "ExampleMap"; // Amazon Location Service Map Name

      // extract the region from the Identity Pool ID
      const region = identityPoolId.split(":")[0];

      async function initializeMap() {
        // Create an authentication helper instance using credentials from Cognito
        const authHelper = await amazonLocationAuthHelper.withIdentityPoolId(identityPoolId);

        // Initialize the map
        const map = new maplibregl.Map({
          container: "map",
          center: [-123.115898, 49.295868],
          zoom: 10,
          style: `https://maps.geo.${region}.amazonaws.com/maps/v0/maps/${mapName}/style-descriptor`,
          ...authHelper.getMapAuthenticationOptions(),
        });
        map.addControl(new maplibregl.NavigationControl(), "top-left");
      }

      initializeMap();
    </script>
  </body>
</html>
```

Running this application displays a full-screen map using your chosen map style. This sample is available in the Amazon Location Service samples repository on [GitHub](https://github.com/aws-samples/amazon-location-samples).

# Using the MapLibre Native SDK for Android with Amazon Location Service
<a name="tutorial-maplibre-android"></a>

Use [MapLibre Native](https://github.com/maplibre/maplibre-gl-native) SDK to embed interactive maps into your Android applications.

The MapLibre Native SDK for Android is a library based on[ Mapbox Native](https://github.com/mapbox/mapbox-gl-native), and is compatible with the styles and tiles provided by the Amazon Location Service Maps API. You can integrate MapLibre Native SDK for Android to embed interactive map views with scalable, customizable vector maps in your Android applications.

This tutorial describes how to integrate the MapLibre Native SDK for Android with Amazon Location. The sample application for this tutorial is available as part of the Amazon Location Service samples repository on [GitHub](https://github.com/aws-samples/amazon-location-samples).

## Building the application: Initialization
<a name="tutorial-maplibre-android-initialization"></a>

To initialize your application:

1. Create a new Android Studio project from the **Empty Activity** template.

1. Ensure that **Kotlin** is selected for the project language.

1. Select a **Minimum SDK of API 14: Android 4.0 (Ice Cream Sandwich)** or newer.

1. Open **Project Structure**, then go to **File** > **Project Structure...** to choose the **Dependencies** section.

1. With **<All Modules>** selected,then choose the **\$1** button to add a new **Library Dependency**.

1. Add **AWS Android SDK** version 2.20.0 or later. For example: `com.amazonaws:aws-android-sdk-core:2.20.0`

1. Add the **MapLibre Native SDK for Android** version 9.4.0 or later. For example: `org.maplibre.gl:android-sdk:9.4.0`

1. At the project level of your **build.gradle** file, add the following maven repository to access the MapLibre packages for Android:

   ```
   allprojects {
       repositories {
           // Retain your existing repositories
           google()
           jcenter()
           
           //  Declare the repositories for MapLibre
           mavenCentral()
       }
   }
   ```

## Building the application: Configuration
<a name="tutorial-maplibre-android-configuration"></a>

To configure your application with your resources and AWS Region:   Create `app/src/main/res/values/configuration.xml`.   Enter the names and identifiers of your resource the AWS Region they were created in:   

```
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="identityPoolId">us-east-1:54f2ba88-9390-498d-aaa5-0d97fb7ca3bd</string>
    <string name="mapName">ExampleMap</string>
    <string name="awsRegion">us-east-1</string>
</resources>
```

## Building the application: Activity layout
<a name="tutorial-maplibre-android-activity-layout"></a>

Edit `app/src/main/res/layout/activity_main.xml`:
+ Add a `MapView`, which renders the map. This will also set the map's initial center point.
+ Add a `TextView`, which displays attribution.

```
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
 
    <com.mapbox.mapboxsdk.maps.MapView
        android:id="@+id/mapView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:mapbox_cameraTargetLat="49.2819"
        app:mapbox_cameraTargetLng="-123.1187"
        app:mapbox_cameraZoom="12"
        app:mapbox_uiAttribution="false"
        app:mapbox_uiLogo="false" />
 
    <TextView
        android:id="@+id/attributionView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#80808080"
        android:padding="5sp"
        android:textColor="@android:color/black"
        android:textSize="10sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        tools:ignore="SmallSp" />
</androidx.constraintlayout.widget.ConstraintLayout>
```

**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](https://docs.aws.amazon.com/location/previous/developerguide/what-is-data-provider.html), make sure to read the [service terms and conditions](https://aws.amazon.com/service-terms/).

## Building the application: Request transformation
<a name="tutorial-maplibre-android-request-transformation"></a>

Create a class named `SigV4Interceptor` to intercept AWS requests and sign them using [Signature Version 4](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html). This will be registered with the HTTP client used to fetch map resources when the Main Activity is created.

```
package aws.location.demo.okhttp
 
import com.amazonaws.DefaultRequest
import com.amazonaws.auth.AWS4Signer
import com.amazonaws.auth.AWSCredentialsProvider
import com.amazonaws.http.HttpMethodName
import com.amazonaws.util.IOUtils
import okhttp3.HttpUrl
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response
import okio.Buffer
import java.io.ByteArrayInputStream
import java.net.URI
 
class SigV4Interceptor(
    private val credentialsProvider: AWSCredentialsProvider,
    private val serviceName: String
) : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val originalRequest = chain.request()
 
        if (originalRequest.url().host().contains("amazonaws.com")) {
            val signer = if (originalRequest.url().encodedPath().contains("@")) {
                // the presence of "@" indicates that it doesn't need to be double URL-encoded
                AWS4Signer(false)
            } else {
                AWS4Signer()
            }
 
            val awsRequest = toAWSRequest(originalRequest, serviceName)
            signer.setServiceName(serviceName)
            signer.sign(awsRequest, credentialsProvider.credentials)
 
            return chain.proceed(toSignedOkHttpRequest(awsRequest, originalRequest))
        }
 
        return chain.proceed(originalRequest)
    }
 
    companion object {
        fun toAWSRequest(request: Request, serviceName: String): DefaultRequest<Any> {
            // clone the request (AWS-style) so that it can be populated with credentials
            val dr = DefaultRequest<Any>(serviceName)
 
            // copy request info
            dr.httpMethod = HttpMethodName.valueOf(request.method())
            with(request.url()) {
                dr.resourcePath = uri().path
                dr.endpoint = URI.create("${scheme()}://${host()}")
 
                // copy parameters
                for (p in queryParameterNames()) {
                    if (p != "") {
                        dr.addParameter(p, queryParameter(p))
                    }
                }
            }
 
            // copy headers
            for (h in request.headers().names()) {
                dr.addHeader(h, request.header(h))
            }
 
            // copy the request body
            val bodyBytes = request.body()?.let { body ->
                val buffer = Buffer()
                body.writeTo(buffer)
                IOUtils.toByteArray(buffer.inputStream())
            }
 
            dr.content = ByteArrayInputStream(bodyBytes ?: ByteArray(0))
 
            return dr
        }
 
        fun toSignedOkHttpRequest(
            awsRequest: DefaultRequest<Any>,
            originalRequest: Request
        ): Request {
            // copy signed request back into an OkHttp Request
            val builder = Request.Builder()
 
            // copy headers from the signed request
            for ((k, v) in awsRequest.headers) {
                builder.addHeader(k, v)
            }
 
            // start building an HttpUrl
            val urlBuilder = HttpUrl.Builder()
                .host(awsRequest.endpoint.host)
                .scheme(awsRequest.endpoint.scheme)
                .encodedPath(awsRequest.resourcePath)
 
            // copy parameters from the signed request
            for ((k, v) in awsRequest.parameters) {
                urlBuilder.addQueryParameter(k, v)
            }
 
            return builder.url(urlBuilder.build())
                .method(originalRequest.method(), originalRequest.body())
                .build()
        }
    }
}
```

## Building the application: Main activity
<a name="tutorial-maplibre-android-request-main-activity"></a>

The Main Activity is responsible for initializing the views that will be displayed to users. This involves:
+ Instantiating an Amazon Cognito `CredentialsProvider`.
+ Registering the Signature Version 4 interceptor.
+ Configuring the map by pointing it at a map style descriptor, and displaying appropriate attribution.

 `MainActivity` is also responsible for forwarding life cycle events to the map view, allowing it to preserve the active viewport between invocations.

```
package aws.location.demo.maplibre
 
import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import aws.location.demo.okhttp.SigV4Interceptor
import com.amazonaws.auth.CognitoCachingCredentialsProvider
import com.amazonaws.regions.Regions
import com.mapbox.mapboxsdk.Mapbox
import com.mapbox.mapboxsdk.maps.MapView
import com.mapbox.mapboxsdk.maps.Style
import com.mapbox.mapboxsdk.module.http.HttpRequestUtil
import okhttp3.OkHttpClient
 
private const val SERVICE_NAME = "geo"
 
class MainActivity : AppCompatActivity() {
    private var mapView: MapView? = null
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
 
        // configuration
        val identityPoolId = getString(R.string.identityPoolId)
        val region = getString(R.string.awsRegion)
        val mapName = getString(R.string.mapName)
 
        // Credential initialization
        val credentialProvider = CognitoCachingCredentialsProvider(
            applicationContext,
            identityPoolId,
            Regions.fromName(identityPoolId.split(":").first())
        )
 
        // initialize MapLibre
        Mapbox.getInstance(this, null)
        HttpRequestUtil.setOkHttpClient(
            OkHttpClient.Builder()
                .addInterceptor(SigV4Interceptor(credentialProvider, SERVICE_NAME))
                .build()
        )
 
        // initialize the view
        setContentView(R.layout.activity_main)
 
        // initialize the map view
        mapView = findViewById(R.id.mapView)
        mapView?.onCreate(savedInstanceState)
        mapView?.getMapAsync { map ->
            map.setStyle(
                Style.Builder()
                    .fromUri("https://maps.geo.${region}.amazonaws.com/maps/v0/maps/${mapName}/style-descriptor")
            ) { style ->
                findViewById<TextView>(R.id.attributionView).text = style.sources.first()?.attribution
            }
        }
    }
 
    override fun onStart() {
        super.onStart()
        mapView?.onStart()
    }
 
    override fun onResume() {
        super.onResume()
        mapView?.onResume()
    }
 
    override fun onPause() {
        super.onPause()
        mapView?.onPause()
    }
 
    override fun onStop() {
        super.onStop()
        mapView?.onStop()
    }
 
    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        mapView?.onSaveInstanceState(outState)
    }
 
    override fun onLowMemory() {
        super.onLowMemory()
        mapView?.onLowMemory()
    }
 
    override fun onDestroy() {
        super.onDestroy()
        mapView?.onDestroy()
    }
}
```

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](https://github.com/aws-samples/amazon-location-samples).

# Using the MapLibre Native SDK for iOS with Amazon Location Service
<a name="tutorial-maplibre-ios"></a>

Use [MapLibre Native SDK for iOS](https://github.com/maplibre/maplibre-gl-native) to embed client-side maps into iOS applications. 

The MapLibre Native SDK for iOS is a library based on [Mapbox GL Native](https://github.com/mapbox/mapbox-gl-native), and is compatible with the styles and tiles provided by the Amazon Location Service Maps API. You can integrate MapLibre Native SDK for iOS to embed interactive map views with scalable, customizable vector maps into your iOS applications.

This tutorial describes how to integrate the MapLibre Native SDK for iOS with Amazon Location. The sample application for this tutorial is available as part of the Amazon Location Service samples repository on [GitHub](https://github.com/aws-samples/amazon-location-samples).

## Building the application: Initialization
<a name="tutorial-maplibre-ios-initialization"></a>

To initialize your application:

1. Create a new Xcode project from the **App** template.

1. Select **SwiftUI** for its interface.

1. Select **SwiftUI** application for its Life Cycle.

1. Select **Swift** for its language.

## Adding MapLibre dependencies using Swift Packages
<a name="tutorial-maplibre-add-dependencies"></a>

To add a package dependency to your Xcode project:

1. Navigate to **File** > **Swift Packages** > **Add Package Dependency**.

1. Enter the repository URL: **https://github.com/maplibre/maplibre-gl-native-distribution**
**Note**  
For more information about Swift Packages see [Adding Package Dependencies to Your App](https://developer.apple.com/documentation/xcode/adding_package_dependencies_to_your_app) at Apple.com

1. In your terminal, install CocoaPods:

   ```
   sudo gem install cocoapods
   ```

1. Navigate to your application's project directory and initialize the **Podfile** with the CocoaPods package manager:

   ```
   pod init
   ```

1. Open the **Podfile** to add `AWSCore` as a dependency:

   ```
   platform :ios, '12.0'
    
   target 'Amazon Location Service Demo' do
     use_frameworks!
    
     pod 'AWSCore'
   end
   ```

1. Download and install dependencies:

   ```
   pod install --repo-update
   ```

1. Open the Xcode workspace that CocoaPods created:

   ```
   xed .
   ```

## Building the application: Configuration
<a name="tutorial-maplibre-ios-configuration"></a>

Add the following keys and values to **Info.plist** to configure the application:


| Key | Value | 
| --- | --- | 
| AWSRegion | us-east-1 | 
| IdentityPoolId | us-east-1:54f2ba88-9390-498d-aaa5-0d97fb7ca3bd | 
| MapName | ExampleMap | 

## Building the application: ContentView layout
<a name="tutorial-maplibre-ios-contentview-layout"></a>

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.

```
import SwiftUI
 
struct ContentView: View {
    @State private var attribution = ""
 
    var body: some View {
        MapView(attribution: $attribution)
            .centerCoordinate(.init(latitude: 49.2819, longitude: -123.1187))
            .zoomLevel(12)
            .edgesIgnoringSafeArea(.all)
            .overlay(
                TextField("", text: $attribution)
                    .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()
    }
}
```

**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](https://docs.aws.amazon.com/location/previous/developerguide/what-is-data-provider.html), make sure to read the [service terms and conditions](https://aws.amazon.com/service-terms/).

## Building the application: Request transformation
<a name="tutorial-maplibre-ios-request-transformation"></a>

Create a new Swift file named `AWSSignatureV4Delegate.swift` containing the following class definition to intercept AWS requests and sign them using [Signature Version 4](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html). An instance of this class will be assigned as the offline storage delegate, which is also responsible for rewriting URLs, in the map view.

```
import AWSCore
import Mapbox
 
class AWSSignatureV4Delegate : NSObject, MGLOfflineStorageDelegate {
    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()
    }
 
    class func doubleEncode(path: String) -> String? {
        return path.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed)?
            .addingPercentEncoding(withAllowedCharacters: .urlPathAllowed)
    }
 
    func offlineStorage(_ storage: MGLOfflineStorage, urlForResourceOf kind: MGLResourceKind, with url: URL) -> URL {
        if url.host?.contains("amazonaws.com") != true {
            // not an AWS URL
            return url
        }
 
        // URL-encode spaces, etc.
        let keyPath = String(url.path.dropFirst())
        guard let percentEncodedKeyPath = keyPath.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else {
            print("Invalid characters in path '\(keyPath)'; unsafe to sign")
            return url
        }
 
        let endpoint = AWSEndpoint(region: region, serviceName: "geo", url: url)
        let requestHeaders: [String: String] = ["host": endpoint!.hostName]
 
        // sign the URL
        let task = AWSSignatureV4Signer
            .generateQueryStringForSignatureV4(
                withCredentialProvider: credentialsProvider,
                httpMethod: .GET,
                expireDuration: 60,
                endpoint: endpoint!,
                // workaround for https://github.com/aws-amplify/aws-sdk-ios/issues/3215
                keyPath: AWSSignatureV4Delegate.doubleEncode(path: percentEncodedKeyPath),
                requestHeaders: requestHeaders,
                requestParameters: .none,
                signBody: true)
        task.waitUntilFinished()
 
        if let error = task.error as NSError? {
            print("Error occurred: \(error)")
        }
 
        if let result = task.result {
            var urlComponents = URLComponents(url: (result as URL), resolvingAgainstBaseURL: false)!
            // re-use the original path; workaround for https://github.com/aws-amplify/aws-sdk-ios/issues/3215
            urlComponents.path = url.path
 
            // have Mapbox GL fetch the signed URL
            return (urlComponents.url)!
        }
 
        // fall back to an unsigned URL
        return url
    }
}
```

## Building the application: Map view
<a name="tutorial-maplibre-ios-request-map-view"></a>

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 SwiftUI
import AWSCore
import Mapbox
 
struct MapView: UIViewRepresentable {
    @Binding var attribution: String
 
    private var mapView: MGLMapView
    private var signingDelegate: MGLOfflineStorageDelegate
 
    init(attribution: Binding<String>) {
        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 region = (regionName as NSString).aws_regionTypeValue()
 
        // MGLOfflineStorage doesn't take ownership, so this needs to be a member here
        signingDelegate = AWSSignatureV4Delegate(region: region, identityPoolId: identityPoolId)
 
        // register a delegate that will handle SigV4 signing
        MGLOfflineStorage.shared.delegate = signingDelegate
 
        mapView = MGLMapView(
            frame: .zero,
            styleURL: URL(string: "https://maps.geo.\(regionName).amazonaws.com/maps/v0/maps/\(mapName)/style-descriptor"))
 
        _attribution = attribution
    }
 
    func makeCoordinator() -> Coordinator {
        Coordinator($attribution)
    }
 
    class Coordinator: NSObject, MGLMapViewDelegate {
        var attribution: Binding<String>
 
        init(_ attribution: Binding<String>) {
            self.attribution = attribution
        }
 
        func mapView(_ mapView: MGLMapView, didFinishLoading style: MGLStyle) {
            let source = style.sources.first as? MGLVectorTileSource
            let attribution = source?.attributionInfos.first
            self.attribution.wrappedValue = attribution?.title.string ?? ""
        }
    }
 
    // MARK: - UIViewRepresentable protocol
 
    func makeUIView(context: UIViewRepresentableContext<MapView>) -> MGLMapView {
        mapView.delegate = context.coordinator
 
        mapView.logoView.isHidden = true
        mapView.attributionButton.isHidden = true
        return mapView
    }
 
    func updateUIView(_ uiView: MGLMapView, context: UIViewRepresentableContext<MapView>) {
    }
 
    // MARK: - MGLMapView proxy
 
    func centerCoordinate(_ centerCoordinate: CLLocationCoordinate2D) -> MapView {
        mapView.centerCoordinate = centerCoordinate
        return self
    }
 
    func zoomLevel(_ zoomLevel: Double) -> MapView {
        mapView.zoomLevel = zoomLevel
        return self
    }
}
```

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](https://github.com/aws-samples/amazon-location-samples).

# Using the Amplify library with Amazon Location Service
<a name="tutorial-map-amplify"></a>

The following tutorial walks you through using AWS Amplify with Amazon Location. Amplify uses MapLibre GL JS to render maps in your JavaScript-based application.

Amplify is a set of open-source client libraries that provide interfaces to different categories of services, including Amplify Geo, which is powered by Amazon Location Service. [ Learn more about the AWS Amplify Geo JavaScript library](https://docs.amplify.aws/lib/geo/getting-started/q/platform/js/).

**Note**  
This tutorial assumes that you have already followed the steps in [Using maps - To add a map to your application](map-prerequisites.md#create-map-resource).

## Building the application: Scaffolding
<a name="tutorial-map-amplify-scaffolding"></a>

This tutorial creates a web application that uses JavaScript to build a map on an HTML page.

Begin by creating an HTML page (`index.html`) that includes the map's container:
+ Enter a `div` element with an `id` of `map` to apply the map's dimensions to the map view. The dimensions are inherited from the viewport.

```
<html>
  <head>
    <style>
      body { margin: 0; }
      #map { height: 100vh; } /* 100% of viewport height */
    </style>
  </head>

  <body>
    <!-- map container -->
    <div id="map" />
  </body>
</html>
```

## Building the application: Adding dependencies
<a name="tutorial-map-amplify-add-dependencies"></a>

Add the following dependencies to your application: 
+ AWS Amplify map and geo libraries.
+ AWS Amplify core library.
+ AWS Amplify auth library.
+ AWS Amplify stylesheet.

```
<!-- CSS dependencies -->
    <link href="https://cdn.amplify.aws/packages/maplibre-gl/1.15.2/maplibre-gl.css" rel="stylesheet" integrity="sha384-DrPVD9GufrxGb7kWwRv0CywpXTmfvbKOZ5i5pN7urmIThew0zXKTME+gutUgtpeD" crossorigin="anonymous" referrerpolicy="no-referrer"></link>

<!-- JavaScript dependencies -->
    <script src="https://cdn.amplify.aws/packages/maplibre-gl/1.15.2/maplibre-gl.js" integrity="sha384-rwYfkmAOpciZS2bDuwZ/Xa/Gog6jXem8D/whm3wnsZSVFemDDlprcUXHnDDUcrNU" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script src="https://cdn.amplify.aws/packages/core/4.3.0/aws-amplify-core.min.js" integrity="sha384-7Oh+5w0l7XGyYvSqbKi2Q7SA5K640V5nyW2/LEbevDQEV1HMJqJLA1A00z2hu8fJ" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script src="https://cdn.amplify.aws/packages/auth/4.3.8/aws-amplify-auth.min.js" integrity="sha384-jfkXCEfYyVmDXYKlgWNwv54xRaZgk14m7sjeb2jLVBtUXCD2p+WU8YZ2mPZ9Xbdw" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script src="https://cdn.amplify.aws/packages/geo/1.1.0/aws-amplify-geo.min.js" integrity="sha384-TFMTyWuCbiptXTzvOgzJbV8TPUupG1rA1AVrznAhCSpXTIdGw82bGd8RTk5rr3nP" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script src="https://cdn.amplify.aws/packages/maplibre-gl-js-amplify/1.1.0/maplibre-gl-js-amplify.umd.min.js" integrity="sha384-7/RxWonKW1nM9zCKiwU9x6bkQTjldosg0D1vZYm0Zj+K/vUSnA3sOMhlRRWAtHPi" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script>
  // application-specific code
</script>
```

This creates an empty page with the map's container. 

## Building the application: Configuration
<a name="tutorial-map-amplify-configuration"></a>

To configure your application using JavaScript:

1. Enter the identifiers of the unauthenticated identity pool that you created in [Using maps - Step 2, Set up authentication](map-prerequisites.md#create-map-resource).

   ```
   // Cognito Identity Pool ID
   const identityPoolId = "region:identityPoolID"; // for example: us-east-1:123example-1234-5678
   // extract the Region from the Identity Pool ID
   const region = identityPoolId.split(":")[0];
   ```

1. Configure AWS Amplify to use the resources you've created, including the identity pool and the Map resource (shown here with the default name of `explore.map`).

   ```
   // Configure Amplify
   const { Amplify } = aws_amplify_core;
   const { createMap } = AmplifyMapLibre;
   
   Amplify.configure({
     Auth: {
       identityPoolId,
       region,
     },
     geo: {
       AmazonLocationService: {
         maps: {
           items: {
             "explore.map": {
               style: "Default style"
             },
           },
           default: "explore.map",
         },
         region,
       },
     }
   });
   ```

## Building the application: Map initialization
<a name="tutorial-map-amplify-request-map-init"></a>

For the map to display after the page is loaded, you must initialize the map. You can adjust the initial map location, add additional controls, and overlay data.

```
async function initializeMap() {
  const map = await createMap(
    {
      container: "map",
      center: [-123.1187, 49.2819],
      zoom: 10,
      hash: true,
    }
  );

  map.addControl(new maplibregl.NavigationControl(), "top-left");
}

initializeMap();
```

**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 `sources.grabmaptiles.attribution` keys. Amplify will automatically provide attribution. When using Amazon Location resources with [data providers](https://docs.aws.amazon.com/location/previous/developerguide/what-is-data-provider.html), make sure to read the [service terms and conditions](https://aws.amazon.com/service-terms/).

## Running the application
<a name="tutorial-map-amplify-writing-js"></a>

You can run this sample application by using it in a local web server, or opening it in a browser.

To use a local web server, you can use npx, installed as part of Node.js, or any other web server of your choice. To use npx, type `npx serve` from within the same directory as `index.html`. This serves the application on `localhost:5000`.

**Note**  
If the policy that you created for your unauthenticated Amazon Cognito role includes a `referer` condition, you might be blocked from testing with `localhost:` URLs. In this case. you can test with a web server that provides a URL that is in your policy.

After completing the tutorial, the final application looks like the following example.

```
<html>
  <head>
    <!-- CSS dependencies -->
    <link href="https://cdn.amplify.aws/packages/maplibre-gl/1.15.2/maplibre-gl.css" rel="stylesheet" integrity="sha384-DrPVD9GufrxGb7kWwRv0CywpXTmfvbKOZ5i5pN7urmIThew0zXKTME+gutUgtpeD" crossorigin="anonymous" referrerpolicy="no-referrer"></link>

    <!-- JavaScript dependencies -->
    <script src="https://cdn.amplify.aws/packages/maplibre-gl/1.15.2/maplibre-gl.js" integrity="sha384-rwYfkmAOpciZS2bDuwZ/Xa/Gog6jXem8D/whm3wnsZSVFemDDlprcUXHnDDUcrNU" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script src="https://cdn.amplify.aws/packages/core/4.3.0/aws-amplify-core.min.js" integrity="sha384-7Oh+5w0l7XGyYvSqbKi2Q7SA5K640V5nyW2/LEbevDQEV1HMJqJLA1A00z2hu8fJ" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script src="https://cdn.amplify.aws/packages/auth/4.3.8/aws-amplify-auth.min.js" integrity="sha384-jfkXCEfYyVmDXYKlgWNwv54xRaZgk14m7sjeb2jLVBtUXCD2p+WU8YZ2mPZ9Xbdw" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script src="https://cdn.amplify.aws/packages/geo/1.1.0/aws-amplify-geo.min.js" integrity="sha384-TFMTyWuCbiptXTzvOgzJbV8TPUupG1rA1AVrznAhCSpXTIdGw82bGd8RTk5rr3nP" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script src="https://cdn.amplify.aws/packages/maplibre-gl-js-amplify/1.1.0/maplibre-gl-js-amplify.umd.min.js" integrity="sha384-7/RxWonKW1nM9zCKiwU9x6bkQTjldosg0D1vZYm0Zj+K/vUSnA3sOMhlRRWAtHPi" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

    <style>
      body { margin: 0; }
      #map { height: 100vh; }
    </style>
  </head>

  <body>
    <div id="map" />
    <script type="module">
      // Cognito Identity Pool ID
      const identityPoolId = "region:identityPoolId"; // for example: us-east-1:123example-1234-5678
      // extract the Region from the Identity Pool ID
      const region = identityPoolId.split(":")[0];

      // Configure Amplify
      const { Amplify } = aws_amplify_core;
      const { createMap } = AmplifyMapLibre;

      Amplify.configure({
        Auth: {
          identityPoolId,
          region,
        },
        geo: {
          AmazonLocationService: {
            maps: {
              items: {
                "explore.map": {
                  style: "Default style"
                },
              },
              default: "explore.map",
            },
            region,
          },
        }
      });

      async function initializeMap() {
        const map = await createMap(
          {
            container: "map",
            center: [-123.1187, 49.2819],
            zoom: 10,
            hash: true,
          }
        );

        map.addControl(new maplibregl.NavigationControl(), "top-left");
      }

      initializeMap();
    </script>
  </body>
</html>
```

Running this application displays a full-screen map using your chosen map style. This sample is also described on the **Embed map** tab of any Map resource page in the [Amazon Location Service console](https://console.aws.amazon.com/location/maps/home).

After you complete this tutorial, go to the [ Display a map](https://docs.amplify.aws/lib/geo/maps/q/platform/js#display-a-map) topic in the AWS Amplify documentation to learn more, including how to display markers on the map.

# Tutorial: using Tangram with Amazon Location Service
<a name="tutorial-tangram"></a>

This section provides the following tutorials on how to integrate Tangram with Amazon Location. 

**Important**  
The Tangram styles in the following tutorials are only compatible with Amazon Location map resources configured with the `VectorHereContrast` style.

The following is an example of an AWS CLI command to create a new map resource called *TangramExampleMap* using the *VectorHereContrast* style:

```
aws --region us-east-1 \
  location \
  create-map \
  --map-name "TangramExampleMap" \
  --configuration "Style=VectorHereContrast"
```

**Note**  
Billing is determined by your usage. You may incur fees for the use of other AWS services. For more information, see [Amazon Location Service pricing](https://aws.amazon.com/location/pricing/).

**Topics**
+ [

# Using Tangram with Amazon Location Service
](tutorial-tangram-js.md)
+ [

# Using Tangram ES for Android with Amazon Location Service
](tutorial-tangram-es-android.md)
+ [

# Using Tangram ES for iOS with Amazon Location Service
](tutorial-tangram-es-ios.md)

# Using Tangram with Amazon Location Service
<a name="tutorial-tangram-js"></a>

[Tangram](https://tangrams.readthedocs.io/) is a flexible mapping engine, designed for real-time rendering of 2D and 3D maps from vector tiles. It can be used with Mapzen-designed styles and the HERE tiles provided by the Amazon Location Service Maps API. This guide describes how to integrate Tangram with Amazon Location within a basic HTML/JavaScript application, although the same libraries and techniques also apply when using frameworks like React and Angular.

Tangram is built atop [Leaflet](https://leafletjs.com/), an open-source JavaScript library for mobile-friendly interactive maps. This means that many Leaflet-compatible plugins and controls also work with Tangram.

Tangram styles built to work with the [Tilezen schema](https://tilezen.readthedocs.io/en/latest/layers/) are largely compatible with Amazon Location when using maps from HERE. These include:
+ [Bubble Wrap](https://github.com/tangrams/bubble-wrap) – A full-featured wayfinding style with helpful icons for points of interest
+ [Cinnabar](https://github.com/tangrams/cinnabar-style) – A classic look and go-to for general mapping applications
+ [Refill ](https://github.com/tangrams/refill-style) – A minimalist map style designed for data visualization overlays, inspired by the seminal Toner style by Stamen Design
+ [Tron](https://github.com/tangrams/tron-style) – An exploration of scale transformations in the visual language of TRON
+ [Walkabout ](https://github.com/tangrams/walkabout-style) – An outdoor-focused style that's perfect for hiking or getting out and about

This guide describes how to integrate Tangram with Amazon Location within a basic HTML/JavaScript application using the Tangram Style called [Bubble Wrap](https://github.com/tangrams/bubble-wrap). This sample is available as part of the Amazon Location Service samples repository on [GitHub](https://github.com/aws-samples/amazon-location-samples).

While other Tangram styles are best accompanied by raster tiles, which encode terrain information, this feature is not 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: Scaffolding
<a name="tutorial-tangram-js-scaffolding"></a>

The application is an HTML page with JavaScript to build the map on your web application. Create an HTML page (`index.html`) and create the map's container: 
+ Enter a `div` element with an `id` of map to apply the map's dimensions to the map view. 
+ The dimensions are inherited from the viewport.

```
<html>
  <head>
    <style>
      body {
        margin: 0;
      }
 
      #map {
        height: 100vh; /* 100% of viewport height */
      }
    </style>
  </head>
  <body>
    <!-- map container -->
    <div id="map" />
  </body>
</html>
```

## Building the application: Adding dependencies
<a name="tutorial-tangram-js-add-dependencies"></a>

Add the following dependencies: 
+ Leaflet and its associated CSS.
+ Tangram.
+ AWS SDK for JavaScript.

```
<!-- CSS dependencies -->
<link
  rel="stylesheet"
  href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
  integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
  crossorigin=""
/>
<!-- JavaScript dependencies -->
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
<script src="https://unpkg.com/tangram"></script>
<script src="https://sdk.amazonaws.com/js/aws-sdk-2.784.0.min.js"></script>
<script>
  // application-specific code
</script>
```

This creates an empty page with the necessary prerequisites. The next step guides you through writing the JavaScript code for your application. 

## Building the application: Configuration
<a name="tutorial-tangram-js-configuration"></a>

To configure your application with your resources and credentials:

1. Enter the names and identifiers of your resources.

   ```
   // Cognito Identity Pool ID
   const identityPoolId = "us-east-1:54f2ba88-9390-498d-aaa5-0d97fb7ca3bd";
   // Amazon Location Service map name; must be HERE-backed
   const mapName = "TangramExampleMap";
   ```

1. Instantiate a credential provider using the unauthenticated identity pool you created in [Using maps - Step 2, Set up authentication](map-prerequisites.md#using-maps-set-up-authentication). Since this uses credentials outside the normal AWS SDK work flow, sessions expire after **one** hour.

   ```
   // extract the region from the Identity Pool ID; this will be used for both Amazon Cognito and Amazon Location
   AWS.config.region = identityPoolId.split(":", 1)[0];
    
   // instantiate a Cognito-backed credential provider
   const credentials = new AWS.CognitoIdentityCredentials({
     IdentityPoolId: identityPoolId,
   });
   ```

1. While Tangram allows you to override the URL(s) used to fetch tiles, it doesn't include the ability to intercept requests so that they can be signed. 

   To work around this, override `sources.mapzen.url` to point to Amazon Location using a synthetic host name `amazon.location`, which will be handled by a [service worker](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API). The following is an example of scene configuration using [Bubble Wrap](https://github.com/tangrams/bubble-wrap):

   ```
   const scene = {
     import: [
       // Bubble Wrap style
       "https://www.nextzen.org/carto/bubble-wrap-style/10/bubble-wrap-style.zip",
       "https://www.nextzen.org/carto/bubble-wrap-style/10/themes/label-7.zip",
       "https://www.nextzen.org/carto/bubble-wrap-style/10/themes/bubble-wrap-road-shields-usa.zip",
       "https://www.nextzen.org/carto/bubble-wrap-style/10/themes/bubble-wrap-road-shields-international.zip",
     ],
     // override values beneath the `sources` key in the style above
     sources: {
       mapzen: {
         // point at Amazon Location using a synthetic URL, which will be handled by the service
         // worker
         url: `https://amazon.location/${mapName}/{z}/{x}/{y}`,
       },
       // effectively disable raster tiles containing encoded normals
       normals: {
         max_zoom: 0,
       },
       "normals-elevation": {
         max_zoom: 0,
       },
     },
   };
   ```

## Building the application: Request transformation
<a name="tutorial-tangram-js-request-transformation"></a>

To register and initialize the service worker, create a `registerServiceWorker` function to be called before the map is initialized. This registers the JavaScript code provided in a separate file called `sw.js` as the service worker controlling `index.html`. 

Credentials are loaded from Amazon Cognito and are passed into the service worker alongside the Region to provide information to sign tile requests with [Signature Version 4](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html). 

```
/**
 * Register a service worker that will rewrite and sign requests using Signature Version 4.
 */
async function registerServiceWorker() {
  if ("serviceWorker" in navigator) {
    try {
      const reg = await navigator.serviceWorker.register("./sw.js");
 
      // refresh credentials from Amazon Cognito
      await credentials.refreshPromise();
 
      await reg.active.ready;
 
      if (navigator.serviceWorker.controller == null) {
        // trigger a navigate event to active the controller for this page
        window.location.reload();
      }
 
      // pass credentials to the service worker
      reg.active.postMessage({
        credentials: {
          accessKeyId: credentials.accessKeyId,
          secretAccessKey: credentials.secretAccessKey,
          sessionToken: credentials.sessionToken,
        },
        region: AWS.config.region,
      });
    } catch (error) {
      console.error("Service worker registration failed:", error);
    }
  } else {
    console.warn("Service worker support is required for this example");
  }
}
```

The Service Worker implementation in `sw.js` listens for `message` events to pick up credential and Region configuration changes. It also acts as a proxy server by listening for `fetch` events. `fetch` events targeting the `amazon.location` synthetic host name will be rewritten to target the appropriate Amazon Location API and signed using Amplify Core's `Signer`.

```
// sw.js
self.importScripts(
  "https://unpkg.com/@aws-amplify/core@3.7.0/dist/aws-amplify-core.min.js"
);
 
const { Signer } = aws_amplify_core;
 
let credentials;
let region;
 
self.addEventListener("install", (event) => {
  // install immediately
  event.waitUntil(self.skipWaiting());
});
 
self.addEventListener("activate", (event) => {
  // control clients ASAP
  event.waitUntil(self.clients.claim());
});
 
self.addEventListener("message", (event) => {
  const {
    data: { credentials: newCredentials, region: newRegion },
  } = event;
 
  if (newCredentials != null) {
    credentials = newCredentials;
  }
 
  if (newRegion != null) {
    region = newRegion;
  }
});
 
async function signedFetch(request) {
  const url = new URL(request.url);
  const path = url.pathname.slice(1).split("/");
 
  // update URL to point to Amazon Location
  url.pathname = `/maps/v0/maps/${path[0]}/tiles/${path.slice(1).join("/")}`;
  url.host = `maps.geo.${region}.amazonaws.com`;
  // strip params (Tangram generates an empty api_key param)
  url.search = "";
 
  const signed = Signer.signUrl(url.toString(), {
    access_key: credentials.accessKeyId,
    secret_key: credentials.secretAccessKey,
    session_token: credentials.sessionToken,
  });
 
  return fetch(signed);
}
 
self.addEventListener("fetch", (event) => {
  const { request } = event;
 
  // match the synthetic hostname we're telling Tangram to use
  if (request.url.includes("amazon.location")) {
    return event.respondWith(signedFetch(request));
  }
 
  // fetch normally
  return event.respondWith(fetch(request));
});
```

To automatically renew credentials and send them to the service worker before they expire, use the following function within `index.html`:

```
async function refreshCredentials() {
  await credentials.refreshPromise();
 
  if ("serviceWorker" in navigator) {
    const controller = navigator.serviceWorker.controller;
 
    controller.postMessage({
      credentials: {
        accessKeyId: credentials.accessKeyId,
        secretAccessKey: credentials.secretAccessKey,
        sessionToken: credentials.sessionToken,
      },
    });
  } else {
    console.warn("Service worker support is required for this example.");
  }
 
  // schedule the next credential refresh when they're about to expire
  setTimeout(refreshCredentials, credentials.expireTime - new Date());
}
```

## Building the application: Map initialization
<a name="tutorial-tangram-js-request-map-init"></a>

For the map to display after the page is loaded, you must initialize the map. You have the option to adjust the initial map location, add additional controls, and overlay data. 

**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.   
Because Tangram doesn't request these resources, and is only compatible with maps from HERE, use "© 2020 HERE". When using Amazon Location resources with [data providers](https://docs.aws.amazon.com/location/previous/developerguide/what-is-data-provider.html), make sure to read the [service terms and conditions](https://aws.amazon.com/service-terms/).

```
/**
 * Initialize a map.
 */
async function initializeMap() {
  // register the service worker to handle requests to https://amazon.location
  await registerServiceWorker();
 
  // Initialize the map
  const map = L.map("map").setView([49.2819, -123.1187], 10);
  Tangram.leafletLayer({
    scene,
  }).addTo(map);
  map.attributionControl.setPrefix("");
  map.attributionControl.addAttribution("© 2020 HERE");
}
 
initializeMap();
```

## Running the application
<a name="tutorial-tangram-js-writing-js"></a>

To run this sample, you can:
+ Use a host that supports HTTPS, 
+ Use a local web server to comply with service worker security restrictions.

To use a local web server, you can use npx, because it's installed as a part of Node.js. You can use `npx serve` from within the same directory as `index.html` and `sw.js`. This serves the application on [localhost:5000](http://localhost:5000/).

The following is the `index.html` file:

```
<!-- index.html -->
<html>
  <head>
    <link
      rel="stylesheet"
      href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
      integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
      crossorigin=""
    />
    <style>
      body {
        margin: 0;
      }
 
      #map {
        height: 100vh;
      }
    </style>
  </head>
 
  <body>
    <div id="map" />
    <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
    <script src="https://unpkg.com/tangram"></script>
    <script src="https://sdk.amazonaws.com/js/aws-sdk-2.784.0.min.js"></script>
    <script>
      // configuration
      // Cognito Identity Pool ID
      const identityPoolId = "<Identity Pool ID>";
      // Amazon Location Service Map name; must be HERE-backed
      const mapName = "<Map name>";
 
      AWS.config.region = identityPoolId.split(":")[0];
 
      // instantiate a credential provider
      credentials = new AWS.CognitoIdentityCredentials({
        IdentityPoolId: identityPoolId,
      });
 
      const scene = {
        import: [
          // Bubble Wrap style
          "https://www.nextzen.org/carto/bubble-wrap-style/10/bubble-wrap-style.zip",
          "https://www.nextzen.org/carto/bubble-wrap-style/10/themes/label-7.zip",
          "https://www.nextzen.org/carto/bubble-wrap-style/10/themes/bubble-wrap-road-shields-usa.zip",
          "https://www.nextzen.org/carto/bubble-wrap-style/10/themes/bubble-wrap-road-shields-international.zip",
        ],
        // override values beneath the `sources` key in the style above
        sources: {
          mapzen: {
            // point at Amazon Location using a synthetic URL, which will be handled by the service
            // worker
            url: `https://amazon.location/${mapName}/{z}/{x}/{y}`,
          },
          // effectively disable raster tiles containing encoded normals
          normals: {
            max_zoom: 0,
          },
          "normals-elevation": {
            max_zoom: 0,
          },
        },
      };
 
      /**
       * Register a service worker that will rewrite and sign requests using Signature Version 4.
       */
      async function registerServiceWorker() {
        if ("serviceWorker" in navigator) {
          try {
            const reg = await navigator.serviceWorker.register("./sw.js");
 
            // refresh credentials from Amazon Cognito
            await credentials.refreshPromise();
 
            await reg.active.ready;
 
            if (navigator.serviceWorker.controller == null) {
              // trigger a navigate event to active the controller for this page
              window.location.reload();
            }
 
            // pass credentials to the service worker
            reg.active.postMessage({
              credentials: {
                accessKeyId: credentials.accessKeyId,
                secretAccessKey: credentials.secretAccessKey,
                sessionToken: credentials.sessionToken,
              },
              region: AWS.config.region,
            });
          } catch (error) {
            console.error("Service worker registration failed:", error);
          }
        } else {
          console.warn("Service Worker support is required for this example");
        }
      }
 
      /**
       * Initialize a map.
       */
      async function initializeMap() {
        // register the service worker to handle requests to https://amazon.location
        await registerServiceWorker();
 
        // Initialize the map
        const map = L.map("map").setView([49.2819, -123.1187], 10);
        Tangram.leafletLayer({
          scene,
        }).addTo(map);
        map.attributionControl.setPrefix("");
        map.attributionControl.addAttribution("© 2020 HERE");
      }
 
      initializeMap();
    </script>
  </body>
</html>
```

The following is the `sw.js` file:

```
// sw.js
self.importScripts(
  "https://unpkg.com/@aws-amplify/core@3.7.0/dist/aws-amplify-core.min.js"
);
 
const { Signer } = aws_amplify_core;
 
let credentials;
let region;
 
self.addEventListener("install", (event) => {
  // install immediately
  event.waitUntil(self.skipWaiting());
});
 
self.addEventListener("activate", (event) => {
  // control clients ASAP
  event.waitUntil(self.clients.claim());
});
 
self.addEventListener("message", (event) => {
  const {
    data: { credentials: newCredentials, region: newRegion },
  } = event;
 
  if (newCredentials != null) {
    credentials = newCredentials;
  }
 
  if (newRegion != null) {
    region = newRegion;
  }
});
 
async function signedFetch(request) {
  const url = new URL(request.url);
  const path = url.pathname.slice(1).split("/");
 
  // update URL to point to Amazon Location
  url.pathname = `/maps/v0/maps/${path[0]}/tiles/${path.slice(1).join("/")}`;
  url.host = `maps.geo.${region}.amazonaws.com`;
  // strip params (Tangram generates an empty api_key param)
  url.search = "";
 
  const signed = Signer.signUrl(url.toString(), {
    access_key: credentials.accessKeyId,
    secret_key: credentials.secretAccessKey,
    session_token: credentials.sessionToken,
  });
 
  return fetch(signed);
}
 
self.addEventListener("fetch", (event) => {
  const { request } = event;
 
  // match the synthetic hostname we're telling Tangram to use
  if (request.url.includes("amazon.location")) {
    return event.respondWith(signedFetch(request));
  }
 
  // fetch normally
  return event.respondWith(fetch(request));
});
```

 This sample is available as part of the Amazon Location Service samples repository on [GitHub](https://github.com/aws-samples/amazon-location-samples).

# Using Tangram ES for Android with Amazon Location Service
<a name="tutorial-tangram-es-android"></a>

[Tangram ES](https://github.com/tangrams/tangram-es) is a C\$1\$1 library for rendering 2D and 3D maps from vector data using OpenGL ES. It's the native counterpart to [Tangram](https://github.com/tangrams/tangram).

Tangram styles built to work with the [Tilezen schema](https://tilezen.readthedocs.io/en/latest/layers/) are largely compatible with Amazon Location when using maps from HERE. These include:
+ [Bubble Wrap](https://github.com/tangrams/bubble-wrap) – A full-featured wayfinding style with helpful icons for points of interest.
+ [Cinnabar](https://github.com/tangrams/cinnabar-style) – A classic look and go-to for general mapping applications.
+ [Refill ](https://github.com/tangrams/refill-style)– A minimalist map style designed for data visualization overlays, inspired by the seminal Toner style by Stamen Design.
+ [Tron](https://github.com/tangrams/tron-style) – An exploration of scale transformations in the visual language of TRON.
+ [Walkabout ](https://github.com/tangrams/walkabout-style)– An outdoor-focused style that's perfect for hiking or getting out and about.

This guide describes how to integrate Tangram ES for Android with Amazon Location using the Tangram style called Cinnabar. This sample is available as part of the Amazon Location Service samples repository on [GitHub](https://github.com/aws-samples/amazon-location-samples).

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
<a name="tutorial-tangram-es-android-initialization"></a>

To initialize your application:

1. Create a new Android Studio project from the **Empty Activity** template.

1. Ensure that **Kotlin** is selected for the project language.

1. Select a **Minimum SDK of API 16: Android 4.1 (Jelly Bean)** or newer.

1. Open **Project Structure** to select **File**, **Project Structure...**, and choose the **Dependencies** section.

1. With **<All Modules>** selected, choose the **\$1** button to add a new **Library Dependency**.

1. Add **AWS Android SDK** version 2.19.1 or later. For example: `com.amazonaws:aws-android-sdk-core:2.19.1`

1. Add **Tangram** version 0.13.0 or later. For example: `com.mapzen.tangram:tangram:0.13.0`. 
**Note**  
Searching for **Tangram**: `com.mapzen.tangram:tangram:0.13.0` will generate a message that it's "not found", but choosing **OK** will allow it to be added.

## Building the application: Configuration
<a name="tutorial-tangram-es-android-configuration"></a>

To configure your application with your resources and AWS Region:

1. Create `app/src/main/res/values/configuration.xml`.

1. Enter the names and identifiers of your resources, and also the AWS Region they were created in:

```
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="identityPoolId">us-east-1:54f2ba88-9390-498d-aaa5-0d97fb7ca3bd</string>
    <string name="mapName">TangramExampleMap</string>
    <string name="awsRegion">us-east-1</string>
    <string name="sceneUrl">https://www.nextzen.org/carto/cinnabar-style/9/cinnabar-style.zip</string>
    <string name="attribution">© 2020 HERE</string>
</resources>
```

## Building the application: Activity layout
<a name="tutorial-tangram-es-android-activity-layout"></a>

Edit `app/src/main/res/layout/activity_main.xml`:
+ Add a `MapView`, which renders the map. This will also set the map's initial center point. 
+ Add a `TextView`, which displays attribution. 

This will also set 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.   
Because Tangram doesn't request these resources, and is only compatible with maps from HERE, use "© 2020 HERE". When using Amazon Location resources with [data providers](https://docs.aws.amazon.com/location/previous/developerguide/what-is-data-provider.html), make sure to read the [service terms and conditions](https://aws.amazon.com/service-terms/).

```
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
 
    <com.mapzen.tangram.MapView
        android:id="@+id/map"
        android:layout_height="match_parent"
        android:layout_width="match_parent" />
 
    <TextView
        android:id="@+id/attributionView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#80808080"
        android:padding="5sp"
        android:textColor="@android:color/black"
        android:textSize="10sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        tools:ignore="SmallSp" />
</androidx.constraintlayout.widget.ConstraintLayout>
```

## Building the application: Request transformation
<a name="tutorial-tangram-es-android-request-transformation"></a>

Create a class named `SigV4Interceptor` to intercept AWS requests and sign them using [Signature Version 4](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html). This will be registered with the HTTP client used to fetch map resources when the Main Activity is created.

```
package aws.location.demo.okhttp
 
import com.amazonaws.DefaultRequest
import com.amazonaws.auth.AWS4Signer
import com.amazonaws.auth.AWSCredentialsProvider
import com.amazonaws.http.HttpMethodName
import com.amazonaws.util.IOUtils
import okhttp3.HttpUrl
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response
import okio.Buffer
import java.io.ByteArrayInputStream
import java.net.URI
 
class SigV4Interceptor(
    private val credentialsProvider: AWSCredentialsProvider,
    private val serviceName: String
) : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val originalRequest = chain.request()
 
        if (originalRequest.url().host().contains("amazonaws.com")) {
            val signer = if (originalRequest.url().encodedPath().contains("@")) {
                // the presence of "@" indicates that it doesn't need to be double URL-encoded
                AWS4Signer(false)
            } else {
                AWS4Signer()
            }
 
            val awsRequest = toAWSRequest(originalRequest, serviceName)
            signer.setServiceName(serviceName)
            signer.sign(awsRequest, credentialsProvider.credentials)
 
            return chain.proceed(toSignedOkHttpRequest(awsRequest, originalRequest))
        }
 
        return chain.proceed(originalRequest)
    }
 
    companion object {
        fun toAWSRequest(request: Request, serviceName: String): DefaultRequest<Any> {
            // clone the request (AWS-style) so that it can be populated with credentials
            val dr = DefaultRequest<Any>(serviceName)
 
            // copy request info
            dr.httpMethod = HttpMethodName.valueOf(request.method())
            with(request.url()) {
                dr.resourcePath = uri().path
                dr.endpoint = URI.create("${scheme()}://${host()}")
 
                // copy parameters
                for (p in queryParameterNames()) {
                    if (p != "") {
                        dr.addParameter(p, queryParameter(p))
                    }
                }
            }
 
            // copy headers
            for (h in request.headers().names()) {
                dr.addHeader(h, request.header(h))
            }
 
            // copy the request body
            val bodyBytes = request.body()?.let { body ->
                val buffer = Buffer()
                body.writeTo(buffer)
                IOUtils.toByteArray(buffer.inputStream())
            }
 
            dr.content = ByteArrayInputStream(bodyBytes ?: ByteArray(0))
 
            return dr
        }
 
        fun toSignedOkHttpRequest(
            awsRequest: DefaultRequest<Any>,
            originalRequest: Request
        ): Request {
            // copy signed request back into an OkHttp Request
            val builder = Request.Builder()
 
            // copy headers from the signed request
            for ((k, v) in awsRequest.headers) {
                builder.addHeader(k, v)
            }
 
            // start building an HttpUrl
            val urlBuilder = HttpUrl.Builder()
                .host(awsRequest.endpoint.host)
                .scheme(awsRequest.endpoint.scheme)
                .encodedPath(awsRequest.resourcePath)
 
            // copy parameters from the signed request
            for ((k, v) in awsRequest.parameters) {
                urlBuilder.addQueryParameter(k, v)
            }
 
            return builder.url(urlBuilder.build())
                .method(originalRequest.method(), originalRequest.body())
                .build()
        }
    }
}
```

## Building the application: Main activity
<a name="tutorial-tangram-es-android-request-main-activity"></a>

The Main Activity is responsible for initializing the views that will be displayed to users. This involves:
+ Instantiating an Amazon Cognito `CredentialsProvider`.
+ Registering the Signature Version 4 interceptor.
+ Configuring the map by pointing it at a map style, overriding tile URLs, and displaying appropriate attribution. 

`MainActivity` is also responsible for forwarding life cycle events to the map view.

```
package aws.location.demo.tangram
 
import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import aws.location.demo.okhttp.SigV4Interceptor
import com.amazonaws.auth.CognitoCachingCredentialsProvider
import com.amazonaws.regions.Regions
import com.mapzen.tangram.*
import com.mapzen.tangram.networking.DefaultHttpHandler
import com.mapzen.tangram.networking.HttpHandler
 
private const val SERVICE_NAME = "geo"
 
class MainActivity : AppCompatActivity(), MapView.MapReadyCallback {
    private var mapView: MapView? = null
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
 
        setContentView(R.layout.activity_main)
 
        mapView = findViewById(R.id.map)
 
        mapView?.getMapAsync(this, getHttpHandler())
        findViewById<TextView>(R.id.attributionView).text = getString(R.string.attribution)
    }
 
    override fun onMapReady(mapController: MapController?) {
        val sceneUpdates = arrayListOf(
            SceneUpdate(
                "sources.mapzen.url",
                "https://maps.geo.${getString(R.string.awsRegion)}.amazonaws.com/maps/v0/maps/${
                    getString(
                        R.string.mapName
                    )
                }/tiles/{z}/{x}/{y}"
            )
        )
 
        mapController?.let { map ->
            map.updateCameraPosition(
                CameraUpdateFactory.newLngLatZoom(
                    LngLat(-123.1187, 49.2819),
                    12F
                )
            )
            map.loadSceneFileAsync(
                getString(R.string.sceneUrl),
                sceneUpdates
            )
        }
    }
 
    private fun getHttpHandler(): HttpHandler {
        val builder = DefaultHttpHandler.getClientBuilder()
 
        val credentialsProvider = CognitoCachingCredentialsProvider(
            applicationContext,
            getString(R.string.identityPoolId),
            Regions.US_EAST_1
        )
 
        return DefaultHttpHandler(
            builder.addInterceptor(
                SigV4Interceptor(
                    credentialsProvider,
                    SERVICE_NAME
                )
            )
        )
    }
 
    override fun onResume() {
        super.onResume()
        mapView?.onResume()
    }
 
    override fun onPause() {
        super.onPause()
        mapView?.onPause()
    }
 
    override fun onLowMemory() {
        super.onLowMemory()
        mapView?.onLowMemory()
    }
 
    override fun onDestroy() {
        super.onDestroy()
        mapView?.onDestroy()
    }
}
```

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](https://github.com/aws-samples/amazon-location-samples).

# Using Tangram ES for iOS with Amazon Location Service
<a name="tutorial-tangram-es-ios"></a>

[Tangram ES](https://github.com/tangrams/tangram-es) is a C\$1\$1 library for rendering 2D and 3D maps from vector data using OpenGL ES. It's the native counterpart to [Tangram](https://github.com/tangrams/tangram).

Tangram styles built to work with the [Tilezen schema](https://tilezen.readthedocs.io/en/latest/layers/) are largely compatible with Amazon Location when using maps from HERE. These include:
+ [Bubble Wrap](https://github.com/tangrams/bubble-wrap) – A full-featured wayfinding style with helpful icons for points of interest
+ [Cinnabar](https://github.com/tangrams/cinnabar-style) – A classic look and go-to for general mapping applications
+ [Refill ](https://github.com/tangrams/refill-style)– A minimalist map style designed for data visualization overlays, inspired by the seminal Toner style by Stamen Design
+ [Tron](https://github.com/tangrams/tron-style) – An exploration of scale transformations in the visual language of TRON
+ [Walkabout](https://github.com/tangrams/walkabout-style) – 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](https://github.com/aws-samples/amazon-location-samples).

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
<a name="tutorial-tangram-es-ios-initialization"></a>

To initialize the application:

1. Create a new Xcode project from the **App** template.

1. Select **SwiftUI** for its interface.

1. Select **SwiftUI** application for its Life Cycle.

1. Select **Swift** for its language.

## Building the application: Add dependencies
<a name="tutorial-tangram-es-ios-add-dependencies"></a>

To add dependencies, you can use a dependency manager, such as [CocoaPods](https://cocoapods.org/): 

1. In your terminal, install CocoaPods:

   ```
   sudo gem install cocoapods
   ```

1. Navigate to your application's project directory and initialize the **Podfile** with the CocoaPods package manager:

   ```
   pod init
   ```

1. Open the **Podfile** to add `AWSCore` and `Tangram-es` as dependencies:

   ```
   platform :ios, '12.0'
    
   target 'Amazon Location Service Demo' do
     use_frameworks!
    
     pod 'AWSCore'
     pod 'Tangram-es'
   end
   ```

1. Download and install dependencies:

   ```
   pod install --repo-update
   ```

1. Open the Xcode workspace that CocoaPods created:

   ```
   xed .
   ```

## Building the application: Configuration
<a name="tutorial-tangram-es-ios-configuration"></a>

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
<a name="tutorial-tangram-es-ios-activity-layout"></a>

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](https://docs.aws.amazon.com/location/previous/developerguide/what-is-data-provider.html), make sure to read the [service terms and conditions](https://aws.amazon.com/service-terms/).

```
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
<a name="tutorial-tangram-es-ios-request-transformation"></a>

Create a new Swift file named `AWSSignatureV4URLHandler.swift` containing the following class definition to intercept AWS requests and sign them using [Signature Version 4](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html). 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
<a name="tutorial-tangram-es-ios-request-main-activity"></a>

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](https://github.com/aws-samples/amazon-location-samples).

# Drawing data features on a map
<a name="drawing-on-a-map"></a>

After you have an application that renders a map, using Amplify, MapLibre, or Tangram to render the map, a natural next step is to draw features on top of the map. For example, you might want to render your customer locations as markers on the map.

In general, you can use the [Places search functions](searching-for-places.md) to find locations from your data, and then use the functionality of Amplify, MapLibre, or Tangram to render the locations.

To see samples of rendering different types of objects on map, see the following MapLibre samples:
+ [Example: Draw markers](example-draw-markers.md)
+ [Example: Draw clustered points](example-draw-clusters.md)
+ [Example: Draw a polygon](example-draw-polygon.md)

For more samples and tutorials, see [Code examples and tutorials for working with Amazon Location Service](samples.md).

# Setting extents for a map using MapLibre
<a name="setting-map-extents"></a>

There are times that you do not want your users to be able to pan or zoom around the entire world. If you are using MapLibre's map control, you can limit the *extents*, or *bounds*, of the map control with the `maxBounds` option, and constrain the zoom with `minZoom` and `maxZoom` options.

The following code example shows how to initialize the map control to constrain panning to a specific boundary (in this case, the extents of the Grab data source).

**Note**  
These samples are in JavaScript, and work within the context of the [Create a web app to use Amazon Location Service](qs-web.md) tutorial.

```
// Set bounds to Grab data provider region 
var bounds = [
        [90.0, -21.943045533438166], // Southwest coordinates
        [146.25, 31.952162238024968] // Northeast coordinates
    ];

var mlglMap = new maplibregl.Map(
    {
    container: 'map',
    style: mapName,
    maxBounds: bounds // Sets bounds as max
    transformRequest,
    }
);
```

Similarly, you can set a minimum and maximum zoom level for the map. The values for both can be between 0 and 24, although the defaults are 0 for minimum zoom and 22 for maximum (data providers may not provide data at all zoom levels. Most map libraries handle this automatically). The following example initializes the `minZoom` and `maxZoom` options on the MapLibre Map control.

```
// Set the minimum and maximum zoom levels 
var mlglMap = new maplibregl.Map(
    {
    container: 'map',
    style: mapName,
    maxZoom: 12,
    minZoom: 5,
    transformRequest,
    }
);
```

**Tip**  
The MapLibre Map control also allows setting these options at runtime, rather than during initialization, with `get...` and `set...` functions. For example, use `getMaxBounds` and `setMaxBounds` to change the map bounds at runtime.

# Managing your map resources with Amazon Location
<a name="managing-maps"></a>

This topic covers the management and configuration of maps within the Amazon Location Service. It explains how to create and customize map resources, enabling you to tailor the mapping experience for your location-based applications.

You can manage your map resources using the Amazon Location console, the AWS CLI, or the Amazon Location APIs.

## List map resources
<a name="viewing-maps"></a>

You can view a list of your map resources using the Amazon Location console, the AWS CLI, or the Amazon Location APIs.

------
#### [ Console ]

**To view a list of existing map resources using the Amazon Location console**

1. Open the Amazon Location console at [https://console.aws.amazon.com/location/](https://console.aws.amazon.com/location/home).

1. Choose **Maps ** from the left navigation pane.

1. View a list of your map resources under **My maps**.

------
#### [ API ]

Use the `[ListMaps](https://docs.aws.amazon.com//location-maps/latest/APIReference/API_ListMaps.html)` operation from the Amazon Location Maps APIs.

The following example is an API request to get a list of map resources in the AWS account.

```
POST /maps/v0/list-maps
```

The following is an example response for `[ListMaps](https://docs.aws.amazon.com//location-maps/latest/APIReference/API_ListMaps.html)`:

```
{
   "Entries": [ 
      { 
         "CreateTime": 2020-10-30T01:38:36Z,
         "DataSource": "Esri",
         "Description": "string",
         "MapName": "ExampleMap",
         "UpdateTime": 2020-10-30T01:38:36Z
      }
   ],
   "NextToken": "1234-5678-9012"
}
```

------
#### [ CLI ]

Use the `[list-map](https://docs.aws.amazon.com/cli/latest/reference/location/list-maps.html)` command.

The following example is an AWS CLI to get a list of map resources in the AWS account. 

```
aws location list-maps
```

------

## Get map resource details
<a name="get-map-details"></a>

You can get details about any map resource in your AWS account using the Amazon Location console, the AWS CLI, or the Amazon Location APIs.

------
#### [ Console ]

**To view the details of a map resource using the Amazon Location console**

1. Open the Amazon Location console at [https://console.aws.amazon.com/location/](https://console.aws.amazon.com/location/home).

1. Choose **Maps ** from the left navigation pane.

1. Under **My maps**, select the name link of the target map resource. 

------
#### [ API ]

Use the `[DescribeMap](https://docs.aws.amazon.com/location-maps/latest/APIReference/API_DescribeMap.html)` operation from the Amazon Location Maps APIs. 

The following example is an API request to get the map resource details for *ExampleMap*.

```
GET /maps/v0/maps/ExampleMap
```

The following is an example response for `[DescribeMap](https://docs.aws.amazon.com/location-maps/latest/APIReference/API_DescribeMap.html)`:

```
{
   "Configuration": { 
      "Style": "VectorEsriNavigation"
   },
   "CreateTime": 2020-10-30T01:38:36Z,
   "DataSource": "Esri",
   "Description": "string",
   "MapArn": "arn:aws:geo:us-west-2:123456789012:maps/ExampleMap",
   "MapName": "ExampleMap",   
   "Tags": { 
      "Tag1" : "Value1" 
   },
   "UpdateTime": 2020-10-30T01:40:36Z
}
```

------
#### [ CLI ]

Use the `[describe-map](https://docs.aws.amazon.com/cli/latest/reference/location/describe-map.html)` command.

The following example is an AWS CLI to get the map resource details for *ExampleMap*.

```
aws location describe-map \
    --map-name "ExampleMap"
```

------

## Delete a map resource
<a name="delete-maps"></a>

You can delete a map resource from your AWS account using the Amazon Location console, the AWS CLI, or the Amazon Location APIs.

**Warning**  
This operation deletes the resource permanently.

 

------
#### [ Console ]

**To delete an existing map resource using the Amazon Location console**

1. Open the Amazon Location console at [https://console.aws.amazon.com/location/](https://console.aws.amazon.com/location/home).

1. Choose **Maps ** from the left navigation pane.

1. Under **My maps** list, select the target map from the list.

1. Choose **Delete map**.

------
#### [ API ]

Use the `[DeleteMap](https://docs.aws.amazon.com/location-maps/latest/APIReference/API_DeleteMap.html)` operation from the Amazon Location Maps APIs. 

The following example is an API request to delete the map resource *ExampleMap*.

```
DELETE /maps/v0/maps/ExampleMap
```

The following is an example success response for `[DeleteMap](https://docs.aws.amazon.com/location-maps/latest/APIReference/API_DeleteMap.html)`:

```
HTTP/1.1 200
```

------
#### [ CLI ]

Use the `[delete-map](https://docs.aws.amazon.com/cli/latest/reference/location/delete-map.html)` command.

The following example is an AWS CLI command to delete the map resource *ExampleMap*.

```
aws location delete-map \
    --map-name "ExampleMap"
```

------