TÍCH HỢP MAPBOX TRÊN NỀN BẢN ĐỒ GOONG TRONG ANDROID

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

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

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

7 usages
              public interface lApiService {
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 placeld, @Query(“api_key”) String apiKey);
1 usage
                      @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 {                                                                                                                                                                                                                                         // Handle error
}                                                                                                                                                                                                                                       }
  @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, getResourcesO.getString(R.string.goong_api_key));
call.enqueue(new Callback<PlaceDetailResponse>() {
 @Override
 public void onResponse(Call<Pl.aceDetailResponse> call, Response<PlaceDetailResponse> response) {
//TODO: Hien thi danh marker cho diem theo toa do                                                                                                                                                           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”))));
}