TÍCH HỢP GOONG MAP SDK VÀO ANDROID

TỔNG QUAN

SDK Bản đồ Android (Android Map SDK) cho phép bạn có thể phát triển ứng dụng với tính năng bản đồ và định vị trên nền tảng Android. Nó bao gồm các công cụ và thư viện để tích hợp bản đồ vào ứng dụng, hỗ trợ người dùng hiển thị và tương tác với bản đồ.

Goong Android SDK cho phép bạn tùy chỉnh bản đồ với nội dung để hiển thị trên thiết bị android;

Goong  Android SDK không chỉ mang hình ảnh thực tế lên trên bản đồ, ngoài ra còn cho phép tương tác và điều chỉnh các đối tượng trên bản đồ của bạn.

Tài liệu dưới đây trình bày cách tính hợp MapLibre 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. Định vị vị trí: Xác định và hiển thị vị trí hiện tại của người dùng trên bản đồ.
  4. 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.
  5. Đánh Dấu Và Chú Thích: Cho phép nhà phát triển thêm các đánh dấu (marker) hoặc chú thích (annotation) vào bản đồ để chỉ ra các vị trí cụ thể.
  6. Tương Tác Người Dùng: Hỗ trợ các thao tác tương tác như phóng to, thu nhỏ, xoay bản đồ, và di chuyển bản đồ.

CÁCH THỨC TÍCH HỢP GOONG MAP SDK VÀO ANDROID

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

    • Truy cập trang https://account.goong.io/keys, sau đó thực hiệntạo key (API key và Maptitles Key)
    • Không cần phải sử dụng token và config token, thư viện free
    • Config key tại file strings.xml

<resources xmlns:tools=”http://schemas.android.com/tools“>
<string name=”app_name“>goong-inapbox</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>

  • Config thư viện:
    1. Thêm `maven { url ‘https://jitpack.io’ }` vào file `settings.gradle` ở root folder như dưới đây:

dependencyResolutionManagement {

repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)

repositories {

google()

mavenCentral()

maven { url ‘https://jitpack.io’ }

}

}

    1. Thêm `goong-map-android-sdk` vào file `build.gradle` của module app (app/build.gradle):

implementation(‘com.github.goong-io:goong-map-android-sdk:1.5@aar’) {

transitive = true

}

    1. Nếu dùng chung với `vtmap-android-sdk`, thì cần tránh duplicate class:

implementation(‘vn.viettelmaps.vtmsdk:vtmap-android-sdk:2.0.2’) {

transitive = true

exclude group: ‘vn.viettelmaps.vtmsdk’, module: ‘vtmap-android-gestures’

}

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

https://rsapi.goong.io//Direction?origin=21.029579719995272%2C105.85242472181584&destination=20.9409074%2C106.2832288&vehicle=car&api_key={{api_key}}

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

4 usages
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;
import retrofit2.http.Query;

7 usages
public interface IApiService {

1 usage
@GET(“/Place/AutoComplete“)
Call<AutoCompleteResponse> getAutoComplete (@Query(“input“) String input, @Query(“api_key“) String apikey);
1 usage
@GET(“/Place/Detail“)
Call<PlaceDetailResponse> getPlaceDetail (@Query(“place_id“) String placeId, @Query(“api_key“) String apikey);
1 usage
@GET(“/Direction“)
Call<DirectionResponse> getDirection (@Query(“origin“) String origin, @Query(“destination“) String destination, @Query(“vehicle“) String vehicle, @Query(“api_key“) String apikey);

}

Các chức năng

      Gắn marker

public void onMap Ready (@NonNull MapLibre Map mapLibreMap) {

   String uri= "https://tiles.goong.io/assets/goong_map_web.json?api_key="
          + "YOUR_MAP_KEY";
          mapLibreMap.setStyle(
          uri,
          style ->
             LatLng start = new LatLng(latitude: 21.029579719995272, longitude: 105.85242472181584); 
             LatLng end = new LatLng(latitude: 20.9409074, longitude: 106.2832288);
             //Chỉ được sử dụng ảnh dạng bitmap, không phải ảnh vector trong trường hợp custom marker 
                                         IconFactory iconFactory = IconFactory.getInstance(MainActivity.this);
                                         Icon icon iconFactory.fromResource(R.drawable.blue_marker_view);

           mapLibreMap.addMarker(new MarkerOptions()
                      .position(start));
           mapLibreMap.addMarker(new MarkerOptions()
           );
                      .position(end)
          CameraPosition position = new CameraPosition.Builder()
      .target (start)
      .zoom (15)
      .tilt (20)
      .build();
mapLibreMap.animateCamera (CameraUpdateFactory.newCameraPosition (position), durationMs: 1200);
this.mapboxMap mapLibreMap;


  • 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_parent" 
android:layout_height="wrap_content">
<!--
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="center_vertical" 
     android:orientation="vertical"
     tools: ignore="Missing Constraints"> 
<TextView
     android:layout_width="match_parent" 
     android:layout_height="wrap_content"
     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="@+id/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õ

  • Xử lý gọi API autocomplete

private void search (String textSearch) {
if (recyclerView != null)
recyclerView.setVisibility(View.VISIBLE);
List<AutoComplete> filteredList = new ArrayList<>();
try {
if (textSearch != null) {
IApiService service Retrofit Instance.getRetrofitInstance(“https://rsapi.goong.io/”).create(IApiService.class);

Call<AutoCompleteResponse> call service.getAutoComplete (textSearch, “YOUR_API_KEY”);
call.enqueue(new Callback<AutoCompleteResponse>() {
@Override
public void onResponse (Call<AutoCompleteResponse> call, Response<AutoCompleteResponse> response) {
 if (response != null && response.isSuccessful()) {
AutoCompleteResponse data = response.body();
if (data != null && data.getPredictions() != null) {
List<AutoComplete> autoCompletes = data.getPredictions();
for (AutoComplete item autocompletes) {
filteredList.add(item);

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

      }
} else {
// Handle error
}
}
@Override
public void onFailure (Call<AutoCompleteResponse> call, Throwable t) {
Toast.makeText(context: MainActivity.this, text: “Auto complete is error!!!”, Toast.LENGTH_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.

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

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

private void fetchDetailLocation(String placeId) {

Style style=mapboxMap.getStyle();
IApiService service =RetrofitInstance.getRetrofitInstance(getResources().getString(R.string.goong_api_url)). create(IApiService.class); Call<PlaceDetailResponse> call = service.getPlaceDetail (placeId, getResources().getString(R.string.goong_api_key)); call.enqueue(new Callback<PlaceDetailResponse>() {

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

//TODO: Hiển thị đánh marker cho điểm theo tọa độ
if (response != null && response.isSuccessful()) {
PlaceDetailResponse data = response.body();
if (data = null && data.getResult() != null && data.getResult().getGeometry() != null) {
Location location = data.getResult().getGeometry().getLocation();
try {

LatLng center = new LatLng(Double.parseDouble(location.getLat()), Double.parseDouble (location.getLng())); drawCircleLineAndMarker (center, style);
selectedLocation = center;
mapboxMap.addMarker(new MarkerOptions()
.position (center));

} catch (Exception e) {
Toast.makeText( context: MainActivity.this, text: “Parsing is error!!!”, Toast.LENGTH_SHORT).show();
}

});

}

}
@Override
public void on Failure (Call<PlaceDetailResponse> call, Throwable t) {
Toast.makeText( context: MainActivity.this, text: “FetchDetailLocation is error!!!”, Toast.LENGTH_SHORT).show();
}

}

}

1.    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

public List<Point> getCircleLatLng(LatLng center, double radius) {

List<Point> result = new ArrayList<>();
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 Math.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.from Lng Lats (polygonCoordinates);

}

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

public void drawCircleLineAndMarker (LatLng center, Style style) {

List<Point> points = this.getCircleLatLng(center, radius: 300);
mapboxMap.clear();
//Vẽ fill color hình tròn
Feature circlePolygon = Feature.fromGeometry (createPolygonFromPoints (points));
style.addSource(new GeoJsonSource( id: “circle_polygon“, circlePolygon));
style.addLayer(new FillLayer (layerld: “polygon-layer“, sourceld: “circle_polygon“)

.withProperties(

PropertyFactory.fillopacity value: 0.2f),
PropertyFactory.fillColor(Color.parseColor(colorString: “#588888“))

));
//Vẽ Line xung quanh
style.addLayer(new Line Layer(layerld: “circle_layer“, sourceld: “circle_source“)

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

PropertyFactory.lineJoin (Property.LINE_JOIN_MITER),
PropertyFactory.lineOpacity(value: .7f), PropertyFactory.lineWidth(value: 0.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.newCameraPosition (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();

IApiService service = RetrofitInstance.getRetrofitInstance(getResources() .getString(R.string.goong_api_url)) .create(IApiService.class); Call<DirectionResponse> call service.getDirection (origin, destination, vehicle: “car”, getResources().getString(R.string.goong_api_key)); call.enqueue(new Callback<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();
drawLineBetween Two Point (points, style);
mapboxMap.clear();
mapboxMap.addMarker(new MarkerOptions()

.position (start));

mapboxMap.addMarker(new MarkerOptions()

.position (end));

bound (start, end);

}

}

}

}
@Override
public void on Failure (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

private void fetchDirections (LatLng start, LatLng end, Style style) {

String origin start.getLatitude() + “,” + start.getLongitude(),

destination = end.getLatitude() + “,” + end.getLongitude();

IApiService service = RetrofitInstance.getRetrofitInstance(getResources() .getString(R.string.goong_api_url)). create(IApiService.class); Call<DirectionResponse> call = service.getDirection (origin, destination, vehicle: “car“, getResources().getString(R.string.goong_api_key)); call.enqueue(new Callback<Direction Response>() {

@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(); drawLineBetween Two Point (points, style);
mapboxMap.clear();
mapboxMap.addMarker(new MarkerOptions()
.position (start));
mapboxMap.addMarker(new MarkerOptions()
.position (end)); bound(start, end);
}

}

}

}
@Override
public void onFailure (Call<DirectionResponse> call, Throwable t) {
Log.d(tag: “Call error“, msg: “Call error“);
}

});

}