MapBox: Alternative of Google Maps API
Mapbox is the location data platform for mobile and web applications
Pengenalan
“Apa itu Mapbox?” Mapbox merupakan salah satu Maps API Services yang memiliki fitur hampir sama seperti Google Maps API. Untuk perbandingan antara Google Maps API dengan Mapbox API bisa kita lihat di situs https://stackshare.io/stackups/google-maps-vs-mapbox. Saya pribadi sendiri lebih memilih ke Mapbox karena, dari segi pricing-nya menurut saya cukup murah dibandingkan dengan Google Maps API selain itu, untuk limitnya yang gratis di mobile saya rasa lebih baik di Mapbox dibandingkan di Google Maps API karena Mapbox menggunakan perhitungan pengguna yang aktif per bulannya. Dan berikut adalah gambaran dari harga di Mapbox.
Dan berikut untuk harga Google Maps API.
Catatan: Kedua harga dari Mapbox dan Google Maps API tersebut bisa berubah sewaktu-waktu ya.
Bagaimana Cara Menggunakannya
Untuk contoh penggunaannya kali ini saya akan membuat sebuah aplikasi di Android yang mana fiturnya seperti berikut.
- Menampilkan peta
- Mengubah gaya peta
- Menambahkan marker
- Menghapus marker
- Mengetahui posisi perangkat saat ini
- Membuat navigasi perjalanan
Registrasi
Sebelum kita bisa mulai menggunakan Mapbox kita diharuskan memiliki access token dimana, access token ini bisa kita dapatkan dengan cara kita daftar terlebih dahulu di situsnya https://www.mapbox.com/. Untuk proses registrasinya tidak akan saya jelaskan ya 😉. Jika kita telah selesai registrasi maka, di halaman dasbornya kita akan melihat nilai access token milik kita.
Contoh Dasar
Silakan buat projek di Android Studio lalu, tambahkan dependency berikut.
implementation 'com.mapbox.mapboxsdk:mapbox-android-sdk:7.1.2'
Kemudian, masukkan file access token yang kita miliki tadi ke file strings.xml seperti berikut.
<resources>
<string name="app_name">Mapbox Sample</string>
<string name="map_box_access_token">pk.eyJ1Ijoia29sb25lbCIsImEiOiJjanNjMWc2enEwMGs5M3lwbmF2NXEwazV4In0.70IXd_Hc-X_o1jQt19J3lA</string>
</resources>
Selanjutnya, buka file main_activity.xml dan ubah menjadi seperti berikut.
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
>
<com.mapbox.mapboxsdk.maps.MapView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/map_view"
app:mapbox_cameraZoom="12"
app:mapbox_cameraTargetLat="3.590000"
app:mapbox_cameraTargetLng="98.678017"
/>
</android.support.constraint.ConstraintLayout>
Pada kode xml diatas, kita ada meng-set property cameraZoom
dengan nilai 12 lalu, posisi kameranya kita masih statik-kan. Sekarang buka file MainActivity.kt dan isi dengan kode berikut.
package com.ysn.mapboxsample
import android.os.Bundle
import android.support.v7.app.AlertDialog
import android.support.v7.app.AppCompatActivity
import com.mapbox.android.core.permissions.PermissionsListener
import com.mapbox.android.core.permissions.PermissionsManager
import com.mapbox.mapboxsdk.Mapbox
import com.mapbox.mapboxsdk.maps.MapboxMap
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback
import com.mapbox.mapboxsdk.maps.Style
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity(), OnMapReadyCallback {
lateinit var permissionsManager: PermissionsManager
lateinit var mapboxMap: MapboxMap
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Mapbox.getInstance(this, getString(R.string.map_box_access_token))
setContentView(R.layout.activity_main)
initMapView(savedInstanceState)
initPermissions()
}
override fun onStart() {
super.onStart()
map_view.onStart()
}
override fun onResume() {
super.onResume()
map_view.onResume()
}
override fun onPause() {
super.onPause()
map_view.onPause()
}
override fun onStop() {
super.onStop()
map_view.onStop()
}
override fun onLowMemory() {
super.onLowMemory()
map_view.onLowMemory()
}
override fun onDestroy() {
super.onDestroy()
map_view.onDestroy()
}
override fun onSaveInstanceState(outState: Bundle?) {
super.onSaveInstanceState(outState)
map_view.onSaveInstanceState(outState!!)
}
private fun initMapView(savedInstanceState: Bundle?) {
map_view.onCreate(savedInstanceState)
}
override fun onMapReady(mapboxMap: MapboxMap) {
this.mapboxMap = mapboxMap
this.mapboxMap.setStyle(Style.MAPBOX_STREETS)
}
private fun initPermissions() {
val permissionListener = object : PermissionsListener {
override fun onExplanationNeeded(permissionsToExplain: MutableList<String>?) {
/* Nothing to do in here */
}
override fun onPermissionResult(granted: Boolean) {
if (granted) {
syncMapbox()
} else {
val alertDialogInfo = AlertDialog.Builder(this@MainActivity)
.setTitle(getString(R.string.info))
.setCancelable(false)
.setMessage(getString(R.string.permissions_denied))
.setPositiveButton(getString(R.string.dismiss)) { _, _ ->
finish()
}
.create()
alertDialogInfo.show()
}
}
}
if (PermissionsManager.areLocationPermissionsGranted(this)) {
syncMapbox()
} else {
permissionsManager = PermissionsManager(permissionListener)
permissionsManager.requestLocationPermissions(this)
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
permissionsManager.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
private fun syncMapbox() {
map_view.getMapAsync(this)
}
}
Dari kode MainActivity.kt kita lihat bahwa kita pertama-tama ada melakukan runtime permission. Jika diizinkan permisinya maka, kita lakukan sync terhadap map_view
dan mengubah gaya petanya menjadi Style.MAPBOX_STREETS
. Dan jangan lupa tambahkan juga permission di file AndroidManifest.xml seperti berikut.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.ysn.mapboxsample">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<application
...
</application>
</manifest>
Sekarang coba jalankan programnya.
Mengubah Gaya Peta
Sekarang coba kita tambahkan fitur untuk mengubah gaya petanya. Di dokumentasinya disebutkan ada beberapa gaya peta yang didukung oleh Mapbox yaitu sebagai berikut.
- Mapbox Street
- Outdoor
- Light and Dark
- Satellite
- Satellite Street
- Traffic Day and Night
Sekarang mari kita buat fitur tersebut di projek kita yang sebelumnya. Pertama-tama kita buat terlebih dahulu fitur tersebut didalam menu options dengan cara tambahkan kode berikut didalam file MainActivity.kt.
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_main, menu)
return super.onCreateOptionsMenu(menu)
}
Lalu kita buat file menu_main.xml di dalam direktori res-menu. Dan isi dengan kode berikut.
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
>
<item
android:id="@+id/menu_item_change_style"
android:title="Change style"
android:icon="@drawable/ic_satellite_black_24dp"
android:orderInCategory="1"
app:showAsAction="never"
/>
</menu>
Lalu kembali lagi ke file MainActivity.kt dan tambahkan kode berikut.
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item?.itemId) {
R.id.menu_item_change_style -> {
val items = arrayOf("Mapbox Street", "Outdoor", "Light", "Dark", "Satellite", "Satellite Street", "Traffic Day", "Traffic Night")
val alertDialogChangeStyleMaps = AlertDialog.Builder(this)
.setItems(items) { dialog, item ->
when (item) {
0 -> {
mapboxMap.setStyle(Style.MAPBOX_STREETS)
dialog.dismiss()
}
1 -> {
mapboxMap.setStyle(Style.OUTDOORS)
dialog.dismiss()
}
2 -> {
mapboxMap.setStyle(Style.LIGHT)
dialog.dismiss()
}
3 -> {
mapboxMap.setStyle(Style.DARK)
dialog.dismiss()
}
4 -> {
mapboxMap.setStyle(Style.SATELLITE)
dialog.dismiss()
}
5 -> {
mapboxMap.setStyle(Style.SATELLITE_STREETS)
dialog.dismiss()
}
6 -> {
mapboxMap.setStyle(Style.TRAFFIC_DAY)
dialog.dismiss()
}
7 -> {
mapboxMap.setStyle(Style.TRAFFIC_NIGHT)
dialog.dismiss()
}
}
}
.setTitle(getString(R.string.change_style_maps))
.create()
alertDialogChangeStyleMaps.show()
}
}
return super.onOptionsItemSelected(item)
}
Sekarang coba jalankan lagi programnya.
Menambahkan Marker
Sekarang coba kita tambahkan fitur untuk menambahkan marker pada peta-nya dengan cara kita tap pada lokasinya dan nanti akan muncul marker-nya. Silakan kita buka file MainActivity.kt dan tambahkan kode berikut didalam method onMapReady seperti berikut.
private val markers = ArrayList<Marker>()
...
override fun onMapReady(mapboxMap: MapboxMap) {
this.mapboxMap = mapboxMap
this.mapboxMap.setStyle(Style.MAPBOX_STREETS)
this.mapboxMap.addOnMapClickListener {
markers.add(
mapboxMap.addMarker(
MarkerOptions().position(it)
)
)
}
}
Sekarang coba jalankan lagi programnya.
Menghapus Marker
Sekarang coba kita buat fitur untuk menghapus marker yang ada dengan cara tap marker yang ada maka marker tersebut akan terhapus dari peta. Sekarang buka file MainActivity.kt dan tambahkan kode berikut didalam method onMapReady.
override fun onMapReady(mapboxMap: MapboxMap) {
...
this.mapboxMap.setOnMarkerClickListener {
for (marker in markers) {
if (marker.position == it.position) {
markers.remove(marker)
mapboxMap.removeMarker(marker)
}
}
true
}
}
Sekarang coba kita jalankan lagi programnya.
Mengetahui Posisi Perangkat Saat Ini
Untuk menambahkan posisi perangkat saat ini kita bisa membuat method baru bernama showingDeviceLocation dan berikut kodenya.
@SuppressLint("MissingPermission")
private fun showingDeviceLocation(mapboxMap: MapboxMap) {
val locationComponent = mapboxMap.locationComponent
locationComponent.activateLocationComponent(this, mapboxMap.style!!)
locationComponent.isLocationComponentEnabled = true
locationComponent.cameraMode = CameraMode.TRACKING
locationComponent.renderMode = RenderMode.COMPASS
}
Lalu, kita panggil method tersebut didalam method onMapReady dan kita masukkan pemanggilannya didalam callback dari setStyle
seperti berikut
override fun onMapReady(mapboxMap: MapboxMap) {
this.mapboxMap = mapboxMap
this.mapboxMap.setStyle(Style.MAPBOX_STREETS) {
showingDeviceLocation(mapboxMap)
}
}
Lalu kita hapus camera position statik yang kita set tadi di file activity_main.xml seperti berikut.
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
>
<com.mapbox.mapboxsdk.maps.MapView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/map_view"
app:mapbox_cameraZoom="12"
app:mapbox_cameraTargetLat="3.590000" <!-- Hapus kode berikut -->
app:mapbox_cameraTargetLng="98.678017" <!-- Hapus kode berikut-->
/>
</android.support.constraint.ConstraintLayout>
Sekarang kita jalankan lagi programnya.
Membuat Navigasi Perjalanan
Sekarang kita akan membuat fitur untuk bisa menampilkan rute dan navigasi perjalanannya. Caranya kita tambahkan terlebih dahulu dependency berikut pada file build.gradle(app)
implementation 'com.mapbox.mapboxsdk:mapbox-android-sdk:7.1.2'
implementation 'com.mapbox.mapboxsdk:mapbox-android-navigation-ui:0.30.0'
Lalu pada build.gradle(Project) kita tambahkan mavenCentral
dan maven
url
seperti berikut.
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.3.21'
repositories {
google()
jcenter()
mavenCentral()
maven {
url 'https://mapbox.bintray.com/mapbox'
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
maven {
url 'https://mapbox.bintray.com/mapbox'
}
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
Sekarang kita buka file activity_main.xml dan tambahkan Button.
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
>
<com.mapbox.mapboxsdk.maps.MapView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/map_view"
app:mapbox_cameraZoom="12"
/>
<!-- Tambahkan kode berikut -->
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/button_start_navigation"
android:text="Start Navigation"
android:layout_marginRight="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
/>
</android.support.constraint.ConstraintLayout>
Sekarang kita buka file MainActivity.kt dan kita ubah didalam method onMapReady didalam callback addOnMapClickListener seperti berikut.
private lateinit var currentRoute: DirectionsRoute
private var navigationMapRoute: NavigationMapRoute? = nulloverride fun onMapReady(mapboxMap: MapboxMap) {
this.mapboxMap = mapboxMap
this.mapboxMap.setStyle(Style.MAPBOX_STREETS) {
showingDeviceLocation(mapboxMap)
}
this.mapboxMap.addOnMapClickListener {
if (markers.size == 2) {
mapboxMap.removeMarker(markers[1])
markers.removeAt(1)
}
markers.add(
mapboxMap.addMarker(
MarkerOptions().position(it)
)
)
if (markers.size == 2) {
val originPoint = Point.fromLngLat(markers[0].position.longitude, markers[0].position.latitude)
val destinationPoint = Point.fromLngLat(markers[1].position.longitude, markers[1].position.latitude)
NavigationRoute.builder(this)
.accessToken(Mapbox.getAccessToken()!!)
.origin(originPoint)
.destination(destinationPoint)
.voiceUnits(DirectionsCriteria.IMPERIAL)
.build()
.getRoute(object : Callback<DirectionsResponse> {
override fun onFailure(call: Call<DirectionsResponse>, t: Throwable) {
Toast.makeText(this@MainActivity, "Error occured: ${t.message}", Toast.LENGTH_LONG)
.show()
}
override fun onResponse(call: Call<DirectionsResponse>, response: Response<DirectionsResponse>) {
if (response.body() == null) {
Toast.makeText(this@MainActivity, "No routes found, make sure you set the right user and access token.", Toast.LENGTH_LONG)
.show()
button_start_navigation.visibility = View.GONE
return
} else if (response.body()!!.routes().size < 1) {
Toast.makeText(this@MainActivity, "No routes found", Toast.LENGTH_LONG)
.show()
button_start_navigation.visibility = View.GONE
return
}
currentRoute = response.body()!!.routes()[0]
if (navigationMapRoute != null) {
navigationMapRoute?.removeRoute()
} else {
navigationMapRoute = NavigationMapRoute(null, map_view, mapboxMap, R.style.NavigationMapRoute)
}
navigationMapRoute?.addRoute(currentRoute)
button_start_navigation.visibility = View.VISIBLE
}
})
} else {
button_start_navigation.visibility = View.GONE
}
true
}
this.mapboxMap.setOnMarkerClickListener {
for (marker in markers) {
if (marker.position == it.position) {
markers.remove(marker)
mapboxMap.removeMarker(marker)
break
}
}
true
}
}
Pada kode diatas, logikanya adalah ketika kita sudah menambahkan marker sebanyak 2 maka, app kita akan melakukan pencarian rute antara marker 1 dan marker 2 dan button_start_navigation
akan muncul. Lalu, kita beri onClickListener
pada button_start_navigation
untuk menjalankan navigasi perjalanannya.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initPermissions()
...
button_start_navigation.setOnClickListener {
val navigationLauncherOptions = NavigationLauncherOptions.builder()
.directionsRoute(currentRoute)
.shouldSimulateRoute(true)
.build()
NavigationLauncher.startNavigation(this, navigationLauncherOptions)
}
}
Perlu diperhatikan pada kode diatas kita ada lihat shouldSimulateRoute
yang mana atribut ini berfungsi jika kita mau melakukan simulasi navigasi perjalanannya maka kita set ke true namun, jika tidak maka kita set ke false. Sekarang coba jalankan programnya.
Catatan: Jika terdapat error seperti berikut ketika menjalankannya.
Maka, solusinya coba kita tambahkan compileOptions
-nya ke Java Versi 1.8 di file build.gradle.
android {
compileSdkVersion 28
defaultConfig {
...
}
buildTypes {
...
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
Kesimpulan
Jadi, kesimpulannya adalah penggunaan Mapbox sebagai alternatif dari Google Maps API saya rasa bisa dipertimbangkan dengan contoh-contoh yang sudah saya berikan pada tulisan ini.