在 Amazon Location Service 上使用 Android 版 Tangram ES - Amazon Location Service

本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。

在 Amazon Location Service 上使用 Android 版 Tangram ES

Tangram ES 是一个 C++ 库,用于使用 OpenGL ES 根据矢量数据渲染 2D 和 3D 地图。它是 Tangram 的原生对应部分。

使用 HERE 中的地图时,专为 Tilezen 架构而设计的 Tangram 风格在很大程度上与 Amazon Location 兼容。其中包括:

  • Bubble Wrap——一种功能齐全的寻路风格,带有用于显示兴趣点的有用图标。

  • Cinnabar——经典外观,是普通测绘应用的首选。

  • Refill——一种极简主义的地图风格,专为数据可视化叠加而设计,灵感来自 Stamen Design 开创性的 Toner 风格。

  • Tron——浏览 TRON 视觉语言中的比例变换

  • Walkabout——以户外为重点的风格,非常适合徒步旅行或外出旅行

本指南介绍如何使用名为 Cinnabar 的 Tangram 风格将 Android 的 Tangram ES 与 Amazon Location 集成。此示例作为 Amazon Location Service 示例存储库的一部分提供GitHub

虽然其他 Tangram 风格最好搭配编码地形信息的栅格图块,但 Amazon Location 尚不支持此功能。

重要

以下教程中的 Tangram 样式仅与使用 VectorHereContrast 样式配置的 Amazon Location 地图资源兼容。

构建应用程序:初始化

要初始化应用程序,请执行以下操作:

  1. 使用空活动模板创建一个新的 Android Studio 项目。

  2. 确保为项目语言选择了 Kotlin

  3. 选择最低 API 16:Android 4.1(Jelly Bean)或更高版本的开发工具包

  4. 打开项目结构,选择文件项目结构...,然后选择依赖项部分。

  5. <All Modules> 选中后,选择 + 按钮以添加新的库依赖项

  6. 添加 AWS Android 开发工具包 2.19.1 或更高版本。例如:com.amazonaws:aws-android-sdk-core:2.19.1

  7. 添加 Tangram 版本 0.13.0 或更高版本。例如:com.mapzen.tangram:tangram:0.13.0

    注意

    搜索 Tangramcom.mapzen.tangram:tangram:0.13.0 将生成一条消息,提示“未找到”,但选择“确定”将允许将其添加。

构建应用程序:配置

要使用您的资源和 AWS 区域配置应用程序,请执行以下操作:

  1. 创建app/src/main/res/values/configuration.xml

  2. 输入您的资源的名称和标识符,以及它们的创建 AWS 区域:

<?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>

构建应用程序:活动布局

编辑 app/src/main/res/layout/activity_main.xml

  • 添加 MapView,用于渲染地图。这还将设置地图的初始中心点。

  • 添加 TextView,显示属性。

这还将设置地图的初始中心点。

注意

您必须在应用程序或文档中为使用的每个数据提供程序提供文字标记或文本属性。属性字符串包含在样式描述符响应中的 sources.esri.attributionsources.here.attributionsource.grabmaptiles.attribution 键下。

由于 Tangram 不请求这些资源,并且仅与 HERE 的地图兼容,因此请使用“© 2020 HERE”。在数据提供程序处使用 Amazon Location 资源时,请务必阅读服务条款和条件

<?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>

构建应用程序:请求转换

创建一个名为 SigV4Interceptor,拦截 AWS 请求的类,并使用 Signature Version 4 对请求进行签名。创建主活动时,将在用于获取地图资源的 HTTP 客户端中进行注册。

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() } } }

构建应用程序:主活动

主活动负责初始化将向用户显示的视图。这涉及:

  • 实例化 Amazon Cognito CredentialsProvider

  • 注册 Signature Version 4 拦截程序。

  • 通过将地图指向地图样式、覆盖图块 URL 并显示适当的属性来配置地图。

MainActivity 还负责将生命周期事件转发到地图视图。

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() } }

运行此应用程序会以您选择的样式显示全屏地图。此示例作为 Amazon Location Service 示例存储库的一部分提供GitHub