Draw a route and show route steps with Huawei — HMS Direction API

Mine KULAÇ AKKULAK
Huawei Developers
Published in
5 min readMar 31, 2020

Hello everyone,

In this tutorial, I will guide you how to use HMS Direction API, draw a route with polyline and show direction steps.

I assume you set up the HMS Map Kit, if not please check this tutorial. Also, for this tutorial we need to use Retrofit and Gson libraries.

Let’s start with adding libraries

dependencies {
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.squareup.retrofit2:retrofit:2.7.2'
implementation 'com.squareup.retrofit2:converter-gson:2.7.2'
}

Define API settings

First, we need to define our retrofit connection

class DirectionsRetrofit {

private val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(Constants().BASE_URL_DIRECTIONS)
.client(setInterceptors())
.addConverterFactory(GsonConverterFactory.create())
.build()

fun <S> createService(serviceClass: Class<S>?): S {
return retrofit.create(serviceClass)
}

private fun setInterceptors() : OkHttpClient{
val logger = HttpLoggingInterceptor()
logger.level = HttpLoggingInterceptor.Level.BODY

return OkHttpClient.Builder()
.readTimeout(60,TimeUnit.SECONDS)
.connectTimeout(60,TimeUnit.SECONDS)
.addInterceptor { chain ->
val url: HttpUrl = chain.request().url().newBuilder()
.addQueryParameter("key", "YOUR_API_KEY")
.build()
val request = chain.request().newBuilder()
.header("Content-Type","application/json")
.url(url)
.build()
chain.proceed(request)
}
.addInterceptor(logger)
.build()
}

}

For Direction API request, API_KEY is necessary. We add the API_KEY query at our HttpClient for every request. (Also you can add the Api Key at Api interface as Retrofit ”@Query”).

You can find API_KEY from agconnect-services.json.

Api url is

val BASE_URL_DIRECTIONS = "https://mapapi.cloud.huawei.com/mapApi/v1/"

Retrofit interface for api service:

interface DirectionsApis {
@POST("routeService/{type}")
fun getDirectionsWithType(
@Path(value = "type",encoded = true) type : String,
@Body directionRequest: DirectionsRequest
): Call<DirectionsResponse>
}

In here, route type can be change, with

enum class DirectionType(val type: String) {
WALKING("walking"),
BICYCLING("bicycling"),
DRIVING("driving")
}

We will add the type programmatically.

Then, we need to define our repo;

open class DirectionsBaseRepo {

private var directionsApis : DirectionsApis? = null

fun getInstance(): DirectionsApis?{
if(directionsApis==null)
setMainApis()
return directionsApis
}

private fun setMainApis(){
directionsApis = DirectionsRetrofit().createService(DirectionsApis::class.java)
}
}

Now, we are ready to request a route.

First we need to define request data structure as follows.

data class DirectionsRequest( 
@SerializedName("origin") val origin: LatLngData,
@SerializedName("destination") val destination: LatLngData )
data class LatLngData (
@SerializedName("lat") val lat: Double,
@SerializedName("lng") val lng: Double )

At our activity/fragment, we can define request object :

private lateinit var directionRequest : DirectionsRequestoverride fun onCreate(savedInstanceState: Bundle?) {
.
.
val origin = LatLngData(40.9702245, 29.0706332)
val destination = LatLngData(41.1106263, 29.0322126)
directionRequest = DirectionsRequest(origin,destination)
.
.
// Let’s request for walking type:
getDirections(DirectionType.WALKING.type,directionRequest)
}
fun getDirections(type: String,directionRequest: DirectionsRequest){
DirectionsBaseRepo().getInstance()?.getDirectionsWithType(type,directionRequest)?
.enqueue(object : Callback<DirectionsResponse>{
override fun onFailure(call: Call<DirectionsResponse>, t: Throwable) {
}

override fun onResponse(call: Call<DirectionsResponse>,
response: Response<DirectionsResponse>) {
if(response.isSuccessful){
response.body()?.let {
onSuccess(it as Any)
}
}
}

})
}

Now, we need to define response data structure.

data class DirectionsResponse (@SerializedName("routes") val routes: List<Routes>,
@SerializedName("returnCode") val returnCode: String,
@SerializedName("returnDesc") val returnDesc: String)
data class Routes (@SerializedName("paths") val paths: List<Paths>,
@SerializedName("bounds") val bounds: Bounds)

data class Paths (@SerializedName("duration") val duration: Double,
@SerializedName("durationText") val durationText: String,
@SerializedName("durationInTraffic") val durationInTraffic: Int,
@SerializedName("distance") val distance: Double,
@SerializedName("startLocation") val startLocation: LatLngData,
@SerializedName("startAddress") val startAddress: String,
@SerializedName("distanceText") val distanceText: String,
@SerializedName("steps") val steps: List<Steps>,
@SerializedName("endLocation") val endLocation: LatLngData,
@SerializedName("endAddress") val endAddress: String)

data class Bounds (@SerializedName("southwest") val southwest: LatLngData,
@SerializedName("northeast") val northeast: LatLngData)

data class Steps (@SerializedName("duration") val duration: Double,
@SerializedName("orientation") val orientation: Double,
@SerializedName("durationText") val durationText: String,
@SerializedName("distance") val distance: Double,
@SerializedName("startLocation") val startLocation: LatLngData,
@SerializedName("instruction") val instruction: String,
@SerializedName("action") val action: String,
@SerializedName("distanceText") val distanceText: String,
@SerializedName("endLocation") val endLocation: LatLngData,
@SerializedName("polyline") val polyline: List<LatLngData>,
@SerializedName("roadName") val roadName: String)

At this point, we have all direction steps, and we need to use all Paths “polyline” values from Steps data to draw a route with Polyline.

private fun addPolyLines(path : Paths){
val options = PolylineOptions()
options.add(LatLng(path.startLocation.lat, path.startLocation.lng))
path.steps.forEach{
it
.polyline.forEach {it1->
options.add(LatLng(it1.lat, it1.lng))
}
}
options.add(LatLng(path.endLocation.lat, path.endLocation.lng))
options.color(Color.BLUE)
options.width(10f)
hMap.addPolyline(options)
}

Let’s request for bicycling type:

getDirections(DirectionType.BICYCLING.type,directionRequest)

Let’s request for driving type:

getDirections(DirectionType.DRIVING.type,directionRequest)

So far, we drew the route on the map. Now let’s show the steps we received from direction api at BottomSheetDialog.

We need to create a layout for BottomSheetDialog. “bottom_sheet_content_view

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">

<TextView
android:id="@+id/directionStepsMainInfo"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:gravity="center_vertical"
android:textColor="@color/colorAccent"
android:textAppearance="?android:attr/textAppearanceLarge" />

<TextView
android:id="@+id/directionStepsVia"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="14sp"
android:gravity="center_vertical"
android:textColor="@color/warm_grey"/>

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/directionStepsRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>

</LinearLayout>

As a note, we can get more than one direction route and paths from Directions API. In this tutorial, we use the first one. Let’s continue with the setting total duration, total distance and first step roadname.

val mainInfo = directionsResponse.routes[0].paths[0].durationText + " " + 
directionsResponse.routes[0].paths[0].distanceText
directionStepsMainInfo.text = mainInfo
directionStepsVia.text = directionsResponse.routes[0].paths[0].steps[0].roadName

We need RecyclerView to show steps detail. Let’s define a layout for item view. “direction_view_holder

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:padding="16dp">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">

<ImageView
android:id="@+id/actionImage"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"/>

<TextView
android:id="@+id/actionDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/colorPrimaryDark"
android:textSize="16sp"
android:layout_gravity="center_vertical"/>

</LinearLayout>

<TextView
android:id="@+id/actionDistanceTime"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/warm_grey"
android:textSize="16sp"
android:layout_marginTop="10dp"
android:layout_marginStart="50dp"/>

</LinearLayout>

And, let’s define the RecyclerView Adaper methods.

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DirectionsHolder {
val inflater = LayoutInflater.from(parent.context)
val view: View = inflater.inflate(R.layout.direction_view_holder, parent, false)
return DirectionsHolder(view)
}
override fun onBindViewHolder(holder: DirectionsHolder, position: Int) {
val step: Steps? = steps[position]
if (step != null) {
holder.actionDescription.text = step.instruction
holder.actionDistanceTime.text = context.getString(R.string.distance_time,
step.distanceText,step.durationText)
when(step.action){
DirectionActionType.STRAIGHT.type-> holder.actionImage.setImageDrawable(
context.resources.getDrawable(R.drawable.ic_go,null))

}
}
}

As you can see, we can see step instruction, duration and time. Also, we can set actions image as we want. Here is the DirectionActionTypes:

enum class DirectionActionType(val type: String) {
STRAIGHT("straight"),
TURN_LEFT("turn-left"),
TURN_RIGHT("turn-right"),
TURN_SLIGHT_LEFT("turn-slight-left"),
TURN_SLIGHT_RIGHT("turn-slight-right"),
TURN_SHARP_LEFT("turn-sharp-left"),
TURN_SHARP_RIGHT("turn-sharp-right"),
UTURN_LEFT("uturn-left"),
UTURN_RIGHT("uturn-right"),
RAMP_LEFT("ramp-left"),
RAMP_RIGHT("ramp-right"),
MERGE("merge"),
FORK_LEFT("fork-left"),
FORK_RIGHT("fork-right"),
FERRY ("ferry"),
FERRY_TRAIN ("ferry-train"),
ROUNDABOUT_LEFT("roundabout-left"),
ROUNDABOUT_RIGHT("roundabout-right"),
END("end"),
UNKOWN("unknown")
}

Last thing, that we need to add :

val directionAdapter = DirectionsAdapter(this,directionsResponse.routes[0].paths[0].steps)
directionStepsRecyclerView.adapter = directionAdapter

If you have any comments or questions, please let me know

--

--