TÍCH HỢP MAPBOX TRÊN NỀN BẢN ĐỒ GOONG TRONG ANDROID – Help Goong (2025)

TỔNG QUAN

Tài liệu dưới đây trình bày cách tính hợp Mapbox trên nền bản đồ của Goong, và sử dụng các dịch vụ cơ bản của Goong, bao gồm:

      1. Hiện các kiểu bản đồ: cơ bản, vệ tinh, tối, sáng,… gắn marker, vẽ vòng tròn bao quanh marker.
      2. Tìm kiếm: nhập tên địa chỉ, hiển thị các gợi ý liên quan tới tên địa chỉ nhập, sau khi chọn thì nhảy ra điểm đó trên bản đồ.
      3. Dẫn đường: nhập tọa độ điểm đầu và cuối, hiển thị đường dẫn trên bản đó, có thông tin về khoảng cách và thời gian di chuyển.

CÁC BƯỚC TÍCH HỢP

Khởi tạo và các thông số cần thiết

<resources xmlns: tools=’http://schemas.android.com/tools’>
<string name=”app_name”>goong-mapbox</string>
<string name=“mapbox_access_token” translatable=”false” tools:ignore=“UnusedResources”>YOUR_MAPBOX_TOKEN</string>
<string name=”goong_api_url”>https://rsapi.goong.io/</string>
<string name=”goong_map_url”>https://tiles.goong.io</string>
<string name=”goong_api_key”>YOUR_API_KEY</string>
<string name=”goong_map_key”>YOUR_MAP_KEY</string>
</resources>|

# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/doc/curent/userquide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. For more details, visit
# https://developer.android.com/r/tool.s/Qradle-multi-project-decoupled-projects
# org.gradle.parallel=true
# AndroidX package structure t。 make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app ‘s APK
# https:/developer. android.com/topic/librarirs/support-library/andoidx-rc android.useAndroidX=true
# Kotlin code style for this project: “official” or “obsolete”:
kotlin. code.style=official
# Enables namespacing of each library’s R class so that its R class includes only the
# resources declared in the library itself and none from the library’s dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
MAPBOX_DOWNLOADS_TOKEN=YOUR_MAPBOX_TOKEN

      1. Các API sử dụng

      • API tìm kiếm bằng autocomplete

https://rsapi.goong.io/Place/AutoComplete?api_key={{api_key}} &location=21.013715429594125%2C%20105.79829597455202&input=H%C3%A0%20N%E1%BB%99i

      • API lấy chi tiết địa điểm

https://rsapi.goong.io//Place/Detail?api_key={{api_key}} &place_id=KS1l5qA4VOcn9IGw1oYZNO5ehPpQbZoT_MXVhz1VUkJQxyQaIyBh8zPa3ZNYCg4MjjUFXF4o_%2FNeuGarUuEvXA%3D%3D.ZXhwYW5kMA%3D%3D\

      • API điều hướng

      • Xem chi tiết tại link: https://document.goong.io/tutorial-Rest-Api.html
      1. Khai báo các API và call

Sử dụng Retrofit để gọi sang API của Goong

      • Add dependencies

implementation ‘com.squareup.retrofit2:retrofit:2.6.4’
implementation ‘com.squareup.retrofit2:converter-gson:2.6.4’

      • Khởi tạo Retrofit instance

public class Retrofitinstance {
3 usages
private static Retrofit retrofit;
3 usages
public static Retrofit getRetrofitInstance(String url) {

if (.retrofit == null) {
retrofit = new Retrofit.Builder()

.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.build () ;

}
return retrofit;

}

      • Tạo service để call API

package com. example.mapbox.api;

import com. example. mapbox. response.AutoCompleteResponse;
import com. example. mapbox. response.DirectionResponse;
import com. example.mapbox. response.PlaceByLatLngResponse;
import com.example.mapbox.response.PlaceDetailResponse;

import retrofit2.Call;
import retrofit2.http.GET;

public interface lApiService {

importretrofit2.http.Query;

@GET(“/Place/AutoComplete”)
Call<AutoCompleteResponse> getAutoComplete(@Query(“input”) String input, @Query(“api_key”) String apiKey);
@GET(“/Place/Detail”)
Call<PlaceDetailResponse> getPlaceDetail(@Query(“place_id”) String placeld, @Query(“api_key”) String apiKey);
@GET(“/Direction”)
Call<DirectionResponse> getDirection((@Query(“origin”) String origin, (@QueryCdestination’) String destination, @Query(“vehicle”) String vehicle, @Query(“api_key”) String apiKey);

Tích hợp các tính năng

Gắn Marker

public void onMapReady(@NonNull MapboxMap mapboxMap) {
String uri = getResources().getString(R.string.goong_map_url) + “/assets/goong.map.web.json?api_key=” + getResource

mapboxMap.setStyle(
new Style.Builder() .fromUri(uri),
new Style.OnStyleLoaded() {
3 usages
@Override
public void onStyleLoaded(@NonNull Style style) {
LatLng start = new bating( latitude: 21.029579719995272, longitude: 105.85242472181584);
LatLng end = new LatLng( latitude: 20.9409074, longitude 106.2832288) ;|
symbolManager = new SymbolManager(mapView, mapboxMap, style);
symbolManager.setIconAllowOveplap(true);
symbolManager.setlconlgnorePl.acement(true);
//chỉ được sử dụng ảnh dạng bitmap, k phải ảnh vector trong TH custom marker
IconFactory iconFactory = IconFactory.getlnstance(MainActivity. this);
Icon icon = iconFactory.fromResource(R.drawable.blue_marker_view);

//Gắn marker vào bản đồ
mapboxMap .AddMarker(new Marker0ptions()
.position(start));
mapboxMap.AddMarker(new Marker0ptions()
.position(end)

);
CameraPosition position = new CameraPosition.Builder()
.target(start)
.zoom(15)

.tilt (20)
.build();
mapboxMap. animateCamera(CameraUpdateFactory. newCameraPosition(position), durationMs: 1200);
}
});
this.mapboxMap = mapboxMap;

}

      • Sử dụng hàm addMarker để thêm marker cho một điểm với tọa độ bất kỳ
      • Trong TH không muốn sử dụng marker có sẵn của Mapbox ta phải sử dụng
      • IconFactory iconFactory = IconFactory.getInstance(MainActivity.this);
        Icon icon = iconFactory.fromResource(R.drawable.blue_marker_view);

mapboxMap.addMarker(new MarkerOptions()
.position(start)
.icon(icon));

Lưu ý: Ảnh sử dụng làm marker thay thế phải dưới dạng bitmap (không được sử dụng ảnh dạng vector)

Tìm kiếm địa điểm

      • Vẽ giao diện tìm kiếm, sử dụng EditText, RecyclerView để làm auto complete

<EditText

android:id=“@+id/search_edit_text”

android:layout_width=“match_parent”

android:layout_height=“32dp”

android:layout_alignParentLeft=”false”

android:layout_alignParentBottom=”false”

android:background=”@color/white”

android:hint=“Search” />

<androidx. recyclerview. widget.RecyclerView
android:layout_marginTop=”32dp”

android:id=“@+id/recycler_view”

android:layout_width=”match_parent”

android:layout_height=”wrap_content”

android:background=”@color/white”

android:layout_below=”@id/LLTopBar” />

      • Tạo 1 layout mới để custom giao diện bên trong mỗi item khi thực hiện tìm kiếm

<?xml version=”1.0″ encoding=“utf-8”?>

<androidx. constraintlayout.widget.ConstraintLayout

xmlns:android=”http://schemas.android.com/apk/res/android”

xmlns:map=”http://schemas. android.com/apk/res-auto”

xmlns:tools=“http://schemas. android.com/tools”

android :layout_width=”match_parenf”

android:layout_height=’wrap_contenf>

<!– Vẽ mỗi item bên trong recycle view–〉
<LinearLayout
android:id=”@+id/llTopBar”

android:layout_width=”match_parent” \

android:layout_height=”wrap_content

android:background=”@color/white”

android:gravity=”centervertical”

android:orientation=”vertical”

tools:ignore=”MissingConstraints”>

<TextView
android:layout_width=”match_parent”

android:layout_height=”wrap_contenf”

android:minHeight=”32dp”

android:paddingleft=”8dp”

android:id=”@+id/suggestion_text”

android:textColor=”@color/black”/>
<TextView
android:layout_width=”0dp”

android:layout_height=”0dp”

android:paddingLeft=”8dp”

android: id=’@+i(l/place_id’

android:visibility=”invisible’/>

</LinearLayout>
</androidx. constraintlayout.widget.ConstraintLayout>

Gán sự kiện cho ô tìm kiếm khi người dùng gõ

searchEditText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {

search(s.toString());
}
@Override
public void afterTextChanged(Editable s) {
});

      • Xử lý gọi API autocomplete

private void search(String textsearch) {

if (recyclerview != null)
recyclerView.setVisibility(View. VISIBLE);
List<AutoComplete> filteredList = new ArrayListoO;
try {

if (textsearch!= null) {lApiService service = Retrofitlnstance.getRetrofltInstance(“https://rsapi.goong.io/”).create(IApiService.class);

CaIl<AutoCompleteResponse>call = service.getAutoComplete(textSearch, “YOUR_API_KEY”);

CaIl.enqueue(new Callback<AutoConpleteResponse>() {

@Override

public void onResponse(Call<AutoConpleteResponse> call, Response

<AutoCompleteResponse> response) {

if (response != null && response.isSuccessful()) {

AutoConpleteResponse data = response.body();

if (data != null && data.getPredictions() != null) {

List<AutoComplete> autoCompletes = data.getPredictions();

for (AutoComplete item : autoConpletes) {

filteredList.add(iten);

}

if (filteredList != null && MainActivity.this.adapter != null) {

MainActivity.this.adapter.updateSuggestions(filteredList);

}

}

} else {

}

}
@Override

public void onFailure(Call<AutoCompIeteResponse> call, Throwable t) {

Toast.makeText( context MainActivity.this, text ‘Auto complete is error!!!,Toast.LENG_SHORT).show();

});

}

} catch (Exception e) {

e.printStackTrace();

}

Lưu ý: Số lần gọi autocomplete thì cần phải tối ưu, tùy theo nhu cầu của ứng dụng, ví dụ theo kiểu sau 2,3 ký tự mới đc gọi hoặc khách nhập nhưng chỉ gọi sau 3s không nhập gì. Vì Goong sẽ tính phí mỗi lần gọi này.

Chi tiết địa điểm được chọn

      • Gọi API lấy chi tiết điểm (Place API)

private void fetchDetailLocation(String placeld) {

Style style = mapboxMap.getStyle();
lApiService service = Retrofitlnstance.getRetrofitInstance(getResources().getString (R.string.goong_api_url)).createdApiService. class);

Call<PlaceDetailResponse> call = service.getPlaceDetaiKplaceld, getResources() .getString(R.string.goong_api_key));

call.enqueue(new Callback<PlaceDetailResponse>() {

@Override
public void onResponse(Call<Pl.aceDetailResponse> call, Response<PlaceDetailResponse> response) {

//TODO: Hiển thị danh marker cho điểm theo tọa độ.

if (response != null && response.isSuccessful()) {

PlaceDetailResponse data = response.body();

if (data != null && data.getResult() != null && data.getResultO.get6eometry() != null) {

Location location = data.getResult().getGeometry().getLocation();

try {

LatLng center = new LatLngCDouble.parseDouDleClocation.getLatO),

Double.parseDouble(location.getLng()));

drawCircleLineAnMarker(center, style);

selectedLocation = center;

Mapbox. AddMarker . (new MarkerOptions()

.position(center));

} catch (Exception e) {

Toast./MakeText(context MainActivity.this, text “Parsing is error!!!”, Toast.LENGTH_SHORT) .show();

}

}

}

}
@Override

public void onFailure(Call<PlaceDetailResponse> call, Throwable t) {

Toast./MakeText( context MainActivity.this, text “FetchDetailLocation is error!!!”,

Toast.LENGTH.SHORT).show();
}

});

}

Vẽ điểm và vòng tròn bao quanh điểm

      • Tính toán các điểm xung quanh điểm ở tâm

/**
*Tính toán các điểm xung quanh hình tròn
* */
public List<Point> getCircleLatLng (latLng center, double radius) {

List<Point> result = new ArrayListo();

int points = 64;

double[ ] [ ] coordinates = new double [points + 1][2];

for (int i = 0; i < points; i++) {

double angle = i * (2 * Math.PI / points);
double dx = radius * Hath.cos(angle);
double dy = radius * Math.sin(angle);
coordinates[i] = new double[ ]{

center.getLongitude() + (dx / 110540f),
center.getLatitude() + (dy / 110540f) };

}

}

coordinates[points] = coordinates[0]; // Close the circle

for (double[ ] coordinate : coordinates) {

result.add(Point.fromLngLat(coordinate[0], coordinate[1]));

}

return result;

}

      • Tạo Polygon

public static Polygon createPolygonFromPoints(List<Point> points)

{ List<List<Point» polygoncoordinates = new ArrayList<>();

polygoncoordinates. add(points);

return Polygon.fro/nLngLatsCpolygonCoordinates);

}

      • Vẽ các điểm xung quanh và marker điểm ở tâm

public void drawCircleLineAndMarkerdating center, Style style) {

List<Point> points = this.getCiPCleLatM(centep, radius: 300);
mapboxMap.Clear();
//Vẽ fill color hình tròn
Feature circlePolygon = Feature.fromGeometry(createPolygonFromPointsCpoints));
style.addSource(new GeoJsonSource( id: “circle.polygon“, circlepolygon));
style.addLayer(new FillLayer( layerid:”polygon-layer“, sourceld:circle.polygon”) . withProperties(
PropertyFactory.fillOpacity( value:0.2f),
PropertyFactory. fillcolor (Color. parseColor( colorstring“588888“))
));
//Vẽ line xung quanh
style. addLayer (new LineLayer( layerid: “circle-layer”, sourceld: “circle-source“) .withProperties(PropertyFactory.lineCap(Property.LINE_CAP_SQUARE),

PropertyFactory.lineJoin(Property.LINE_JOIN_MITER),
PropertyFactory.lineOpacity ( value.7f),
PropertyFactory.lineWidth ( value0.3f),
PropertyFactory.lineColor(Color.parSeColor( colorstring“#588888“))));

//Point camera về điểm được chọn
CameraPosition position = new CameraPosition.Builder()

.target(center)
.zoom(15) .tilt(20)
.build();

mapboxMap. animateCamera (CameraUpdateFactory. neivCameraPosition (position), durationMs: 1200);

}

Dẫn đường

      • Gọi API lấy thông tin dẫn đường giữa 2 điểm

private void fetchDirections(LatLng start, LatLng end, Style style) {
String origin = start .getLatitude() + “, ” + start .getLongitude(),
destination = end.getLatitude() + “,” + end.getLongitude();
lApiService service =Retrofitinstance.getRetrofitInstance(getResources().getString (R.string.goong_api_url)) .createdApiService.class);
Call.<DirectionResponse> call = service.getDirection(origin, destination, vehicle: “car”, getResources().getString(R.string.goong_api_key));

call.enqueue(new CaIlback<DirectionResponse>() {

@Override

public void onResponse(Call<DirectionResponse> call, Response<DirectionResponse> response) {
//Gọi API lấy danh sách các điểm

if (response != null && response.isSuccessful()) {
DirectionResponse data = response.body();
if (data != null && data.getRoutes() != null) {
Route route = data.getRoutes().get(0);
if (route.getOverviewPolyline() != null && route.getOverviewPolyline() != null &&
route.getOverviewPolyline().getPoints() != null) {
String geometry = route.getOverviewPolyline().getPoints();
List<Point> points = Linestring.fromPolyline(.geometry, precision: 5) .coordinates();
drawLineBetweenTwoPoint(points, style);
mapboxMap.Clear();
mapBoxMap.addMapker(new MakerOptions()
.position(start));
mapboxMap .addMaker(new MakerOptions()
.position(end));
bound(start, end);

}

}

}

}
@Override

public void onFailure(Call<DirectionResponse> call, Throwable t) {

Log.d( tag: “Call error“, msg: “call error”);

}

});

}

      • Lấy danh sách điểm

Cài đặt thêm dependencies:

implementation ‘com.mapbox.mapboxsdk:mapbox-android-plugin-annotation-v9:0.9.0’

Trong kết quả trả về có overview_polyline, trong đó có points đã được mã hóa, sử dụng LineString để tạo đường line từ danh sách các điểm

LineString.fromPolyline(geometry, 5).coordinates();

Sử dụng GeoJson để vẽ line giữa 2 điểm

public void drawLineBetweenTwoPoint(List<Point> points, Style style) {

Linestring linestring = Linestring.fromLngLats(points);
Feature feature = Feature.fromGeometry(linestring);
GeoJsonSource geoJsonSource = new GeoJsonSource( id: “line-source“, feature);
style.addSource(geoJsonSource);
style.addLayer(new LineLayer( layerld: “linelayer”, sourceld: “line-source”)

.withProperties(PropertyFactory.lineCap(Property.LINE_CAP_SQUARE),

PropertyFactory .lineJoin(Property.LINE_JOIN_MITER),
PropertyFactory.line0pacity( value: .7f),
PropertyFactory.linelVidth( value 7f),
PropertyFactory.lineColor(Color.parseColor( colorstring“#3887be”))));

}

TÍCH HỢP MAPBOX TRÊN NỀN BẢN ĐỒ GOONG TRONG ANDROID – Help Goong (2025)

References

Top Articles
Latest Posts
Recommended Articles
Article information

Author: Domingo Moore

Last Updated:

Views: 5891

Rating: 4.2 / 5 (53 voted)

Reviews: 92% of readers found this page helpful

Author information

Name: Domingo Moore

Birthday: 1997-05-20

Address: 6485 Kohler Route, Antonioton, VT 77375-0299

Phone: +3213869077934

Job: Sales Analyst

Hobby: Kayaking, Roller skating, Cabaret, Rugby, Homebrewing, Creative writing, amateur radio

Introduction: My name is Domingo Moore, I am a attractive, gorgeous, funny, jolly, spotless, nice, fantastic person who loves writing and wants to share my knowledge and understanding with you.