

# 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).