Related
This article is orginally from HUAWEI Developer Forum.
Forum link: https://forums.developer.huawei.com/forumPortal/en/home
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Before we start learning about today’s topic, I strongly recommend you to go through my previous article i.e. HMS Site Map (Part 1). It will help you to have a clear picture.
Let’s Begin
In the previous article, we were successfully able to get details after selecting the place that we want to search using Site Kit. Today in this article we are going to see how to show a map using Map Kit after fetching the Latitude and Longitude from the details we selected. Also we are going to see how to use the Site APIs and Map APIs using POSTMAN in our Part 3 article.
One Step at a time
First we need to add Map Kit dependencies in the app gradle file and sync the app.
implementation 'com.huawei.hms:maps:4.0.1.300'
After adding the dependencies we need to provide permission in AndroidManifest.xml file.
Code:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="com.huawei.appmarket.service.commondata.permission.GET_COMMON_DATA"/>
Let’s Code
Main Activity class
Code:
private void showDetails(String item) {
String pattern = Pattern.quote("\\" + "n");
String[] lines = item.split("\\n+");
autoCompleteTextView.setText(lines[0]);
mLat = lines[2]; // This is latitude
mLon = lines[3]; // This is longitude
title = lines[0]; // This is title or place name
String details = "<font color='red'>PLACE NAME : </font>" + lines[0] + "<br>"
+ "<font color='#CD5C5C'>COUNTRY : </font>" + lines[1] + "<br>"
+ "<font color='#8E44AD'>ADDRESS : </font>" + lines[4] + "<br>"
+ "<font color='#008000'>PHONE : </font>" + lines[5];
txtDetails.setText(Html.fromHtml(details, Html.FROM_HTML_MODE_COMPACT));
}
private void showMap(){
Intent intent = new Intent(MainActivity.this, MapActivity.class);
intent.putExtra("lat",mLat); // Here we are passing Latitude and Longitude
intent.putExtra("lon",mLon); // and titile from MainActivity class to
intent.putExtra("title",title);// MapActivity class…
startActivity(intent);
}v
Main Code
1) First we need to understand whether we are showing the map in view or fragment. Because there are two way we can show our map.
a) Fragment way
In fragment way we add MapFragment in the layout file of an activity.
Code:
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:map="http://schemas.android.com/apk/res-auto"
android:id="@+id/mapfragment_mapfragmentdemo"
class="com.huawei.hms.maps.MapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
map:cameraTargetLat="48.893478"
map:cameraTargetLng="2.334595"
map:cameraZoom="10" />
b) MapView way
Here we add MapView in the layout file of an activity.
Code:
<com.huawei.hms.maps.MapView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:map="http://schemas.android.com/apk/res-auto"
android:id="@+id/mapView"
android:layout_width="match_parent"
android:layout_height="match_parent"
map:mapType="normal"
map:uiCompass="true"
map:uiZoomControls="true"
map:cameraTargetLat="51"
map:cameraTargetLng="10"
map:cameraZoom="8.5"/>
2) Here we are going with MapView.
3) For both Fragment as well as View, we need to implement OnMapReadyCallback API in our MapActivity to use a Map. After implementing this API, it will ask to implement onMapReady method.
Code:
public void onMapReady(HuaweiMap map) {
Log.d(TAG, "onMapReady: ");
hMap = map;
}
4) The only difference which we will see between MapFragment and MapView is instantiating Map.
a) MapFragement
Code:
private MapFragment mMapFragment;
mMapFragment = (MapFragment) getFragmentManager()
.findFragmentById(R.id.mapfragment_mapfragmentdemo);
mMapFragment.getMapAsync(this);
b) MapView
Code:
private MapView mMapView;
mMapView = findViewById(R.id.mapview_mapviewdemo);
Bundle mapViewBundle = null;
if (savedInstanceState != null) {
mapViewBundle = savedInstanceState.getBundle("MapViewBundleKey");
}
mMapView.onCreate(mapViewBundle);
mMapView.getMapAsync(this);
5) Permission we need to check
Code:
//Put this in the top of the onCreate() method …
private static final String[] RUNTIME_PERMISSIONS = {
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.INTERNET
};
// This will placed in the onCreate() method …
if (!hasPermissions(this, RUNTIME_PERMISSIONS)) {
ActivityCompat.requestPermissions(this, RUNTIME_PERMISSIONS, REQUEST_CODE);
}
// Use this method to check Permission …
private static boolean hasPermissions(Context context, String... permissions) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && permissions != null) {
for (String permission : permissions) {
if (ActivityCompat.checkSelfPermission(context, permission)
!= PackageManager.PERMISSION_GRANTED) {
return false;
}
}
}
return true;
}
MapActivity Class
Code:
public class MapActivity extends AppCompatActivity implements OnMapReadyCallback {
private static final String TAG = "MapActivity";
private MapView mMapView;
private HuaweiMap hmap;
private Marker mMarker;
private static final String[] RUNTIME_PERMISSIONS = {
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.INTERNET
};
private static final String MAPVIEW_BUNDLE_KEY = "MapViewBundleKey";
private static final int REQUEST_CODE = 100;
private String mLatitude, mLongitude,mTitle;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_map);
mLatitude = getIntent().getExtras().getString("lat");
mLongitude = getIntent().getExtras().getString("lon");
mTitle = getIntent().getExtras().getString("title");
if (!hasPermissions(this, RUNTIME_PERMISSIONS)) {
ActivityCompat.requestPermissions(this, RUNTIME_PERMISSIONS, REQUEST_CODE);
}
mMapView = findViewById(R.id.mapView);
Bundle mapViewBundle = null;
if (savedInstanceState != null) {
mapViewBundle = savedInstanceState.getBundle(MAPVIEW_BUNDLE_KEY);
}
mMapView.onCreate(mapViewBundle);
mMapView.getMapAsync(this);
}
@Override
protected void onStart() {
super.onStart();
mMapView.onStart();
}
@Override
protected void onStop() {
super.onStop();
mMapView.onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
mMapView.onDestroy();
}
@Override
protected void onPause() {
mMapView.onPause();
super.onPause();
}
@Override
protected void onResume() {
super.onResume();
mMapView.onResume();
}
@Override
public void onLowMemory() {
super.onLowMemory();
mMapView.onLowMemory();
}
@Override
public void onMapReady(HuaweiMap huaweiMap) {
Log.d(TAG, "onMapReady: ");
hmap = huaweiMap;
hmap.setMyLocationEnabled(true);
hmap.setMapType(HuaweiMap.MAP_TYPE_NORMAL);
hmap.setMaxZoomPreference(15);
hmap.setMinZoomPreference(5);
CameraPosition build = new CameraPosition.Builder()
.target(new LatLng(Double.parseDouble(mLatitude), Double.parseDouble(mLongitude)))
.build();
CameraUpdate cameraUpdate = CameraUpdateFactory
.newCameraPosition(build);
hmap.animateCamera(cameraUpdate);
MarkerOptions options = new MarkerOptions()
.position(new LatLng(Double.parseDouble(mLatitude),
Double.parseDouble(mLongitude)))
.title(mTitle);
mMarker = hmap.addMarker(options);
mMarker.showInfoWindow();
hmap.setOnMarkerClickListener(new HuaweiMap.OnMarkerClickListener() {
@Override
public boolean onMarkerClick(Marker marker) {
Toast.makeText(getApplicationContext(), "onMarkerClick:" +
marker.getTitle(), Toast.LENGTH_SHORT).show();
return false;
}
});
}
private static boolean hasPermissions(Context context, String... permissions) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && permissions != null) {
for (String permission : permissions) {
if (ActivityCompat.checkSelfPermission(context, permission)
!= PackageManager.PERMISSION_GRANTED) {
return false;
}
}
}
return true;
}
}
Core Functionality of Map
1) Types of Map
There are five types map:
· HuaweiMap.MAP_TYPE_NORMAL
· HuaweiMap.MAP_TYPE_NONE
· HuaweiMap.MAP_TYPE_SATELLITE
· HuaweiMap.MAP_TYPE_HYBRID
· HuaweiMap.MAP_TYPE_TERRAIN
But we can only use MAP_TYPE_NORMAL and MAP_TYPE_NONE. Normal type is a standard map, which shows roads, artificial structures, and natural features such as rivers. None type is an empty map without any data.
The Rest Map type is in development phase.
2) Camera Movement
Huawei maps are moved by simulating camera movement. You can control the visible region of a map by changing the camera's position. To change the camera's position, create different types of CameraUpdate objects using the CameraUpdateFactory class, and use these objects to move the camera.
Code:
CameraPosition build = new CameraPosition.Builder().target(new
LatLng(Double.parseDouble(mLatitude),
Double.parseDouble(mLongitude))).build();
CameraUpdate cameraUpdate = CameraUpdateFactory
.newCameraPosition(build);
hmap.animateCamera(cameraUpdate);
In the above code we are using Map camera in animation mode. When moving the map camera in animation mode, you can set the animation duration and API to be called back when the animation stops. By default, the animation duration is 250 ms.
3) My Location in Map
We can get our location in our Map by simply enabling my-location layer. Also, we can display my-location icon in the Map.
Code:
hmap.setMyLocationEnabled(true);
hmap.getUiSettings().setMyLocationButtonEnabled(true);
4) Show Marker in Map
We can add markers to a map to identify locations such as stores and buildings, and provide additional information with information windows.
Code:
MarkerOptions options = new MarkerOptions()
.position(new LatLng(Double.parseDouble(mLatitude),
Double.parseDouble(mLongitude)))
.title(mTitle); // Adding the title here …
mMarker = hmap.addMarker(options);
mMarker.showInfoWindow();
We can customize our marker according to our need using BitmapDescriptor object.
Code:
Bitmap bitmap = ResourceBitmapDescriptor.drawableToBitmap(this,
ContextCompat.getDrawable(this, R.drawable.badge_ph));
BitmapDescriptor bitmapDescriptor = BitmapDescriptorFactory.fromBitmap(bitmap);
mMarker.setIcon(bitmapDescriptor);
We can title to the Marker as shown in the above code. We can also make the marker clickable as shown below.
Code:
hmap.setOnMarkerClickListener(new HuaweiMap.OnMarkerClickListener() {
@Override
public boolean onMarkerClick(Marker marker) {
Toast.makeText(getApplicationContext(), "onMarkerClick:" +
marker.getTitle(), Toast.LENGTH_SHORT).show();
return false;
}
});
5) Map comes in shape
a) Polyline
b) Polygon
c) Circle
We can use Polyline if we need to show routes from one place to another. We can combine Direction API with Polyline to show routes for walking, bicycling and driving also calculating routes distance.
If we need to show radius like the location under 500 meter or something we use Circle shape to show in the map.
The Result
Any questions about this process, you can try to acquire answers from HUAWEI Developer Forum.
More information like this, you can visit HUAWEI Developer Forum
Original link: https://forums.developer.huawei.com/forumPortal/en/topicview?tid=0202330537081990041&fid=0101187876626530001
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Article Introduction
In this article we are going to cover HUAWEI Map Kit JavaScript API introduction. Next we going to implementing HUAWEI Map in Ionic/Cordova project. Lastly we will implement HUAWEI Map Kit JavaScript into Native Application.
Technology Introduction
HUAWEI Map Kit provides JavaScript APIs for you to easily build map apps applicable to browsers.
It provides the basic map display, map interaction, route planning, place search, geocoding, and other functions to meet requirements of most developers.
Restriction
Before using the service, you need to apply for an API key on the HUAWEI Developers website. For details, please refer to "Creating an API Key" in API Console Operation Guide. To enhance the API key security, you are advised to restrict the API key. You can configure restrictions by app and API on the API key editing page.
Generating API Key
Go to HMS API Services > Credentials and click Create credential.
Click API key to generate new API Key.
In the dialog box that is displayed, click Restrict to set restrictions on the key to prevent unauthorized use or quota theft. This step is optional.
The restrictions include App restrictions and API restriction.
App restrictions: control which websites or apps can use your key. Set up to one app restriction per key.
API restrictions: specify the enabled APIs that this key can call.
After setup App restriction and API restriction API key will generate.
The API key is successfully created. Copy API Key to use in your project.
Huawei Web Map API introduction
1. Make a Basic Map
Code:
function loadMapScript() {
const apiKey = encodeURIComponent(
"API_KEY"
);
const src = `https://mapapi.cloud.huawei.com/mapjs/v1/api/js?callback=initMap&key=${apiKey}`;
const mapScript = document.createElement("script");
mapScript.setAttribute("src", src);
document.head.appendChild(mapScript);
}
function initMap() { }
function initMap() {
const mapOptions = {};
mapOptions.center = { lat: 48.856613, lng: 2.352222 };
mapOptions.zoom = 8;
mapOptions.language = "ENG";
const map = new HWMapJsSDK.HWMap(
document.getElementById("map"),
mapOptions
);
}
loadMapScript();
Note: Please update API_KEY with the key which you have generated. In script url we are declaring callback function, which will automatically initiate once Huawei Map Api loaded successfully.
2. Map Interactions
Map Controls
Code:
var mapOptions = {};
mapOptions.center = {lat: 48.856613, lng: 2.352222};
mapOptions.zoom = 10;
scaleControl
mapOptions.scaleControl = true; // Set to display the scale.
mapOptions.scaleControlOptions = {
units: "imperial" // Set the scale unit to inch.
};
zoomSlider
Code:
mapOptions.zoomSlider = true ; // Set to display the zoom slider.
zoomControl
Code:
mapOptions.zoomControl = false; // Set not to display the zoom button.
rotateControl (Manage Compass)
Code:
mapOptions.rotateControl = true; // Set to display the compass.
navigationControl
Code:
mapOptions.navigationControl = true; // Set to display the pan button.
copyrightControl
Code:
mapOptions.copyrightControl = true; // Set to display the copyright information.
mapOptions.copyrightControlOptions = {value: "HUAWEI",} // Set the copyright information.
locationControl
Code:
mapOptions.locationControl= true; // Set to display the current location.
Camera
Map moving: You can call the map.panTo(latLng)
Map shift: You can call the map.panBy(x, y)
Zoom: You can use the map.setZoom(zoom) method to set the zoom level of a map.
Area control: You can use map.fitBounds(bounds) to set the map display scope.
Map Events
Map click event:
Code:
map.on('click', () => {
map.zoomIn();
});
Map center change event:
Code:
map.onCenterChanged(centerChangePost);
function centerChangePost() {
var center = map.getCenter();
alert( 'Lng:'+map.getCenter().lng+'
'+'Lat:'+map.getCenter().lat);
}
Map heading change event:
Code:
map.onHeadingChanged(headingChangePost);
function headingChangePost() {
alert('Heading Changed!');
}
Map zoom level change event:
Code:
map.onZoomChanged(zoomChangePost);
function zoomChangePost() {
alert('Zoom Changed!')
}
3. Drawing on Map
Marker:
You can add markers to a map to identify locations such as stores and buildings, and provide additional information with information windows.
Code:
var map;
var mMarker;
function initMap() {
var mapOptions = {};
mapOptions.center = {lat: 48.856613, lng: 2.352222};
mapOptions.zoom = 8;
map = new HWMapJsSDK.HWMap(document.getElementById('map'), mapOptions);
mMarker = new HWMapJsSDK.HWMarker({
map: map,
position: {lat: 48.85, lng: 2.35},
zIndex: 10,
label: 'A',
icon: {
opacity: 0.5
}
});
}
Marker Result:
Marker Clustering:
The HMS Core Map SDK allows you to cluster markers to effectively manage them on the map at different zoom levels. When a user zooms in on the map to a high level, all markers are displayed on the map. When the user zooms out, the markers are clustered on the map for orderly display.
Code:
var map;
var markers = [];
var markerCluster;
var locations = [
{lat: 51.5145160, lng: -0.1270060},
{ lat : 51.5064490, lng : -0.1244260 },
{ lat : 51.5097080, lng : -0.1200450 },
{ lat : 51.5090680, lng : -0.1421420 },
{ lat : 51.4976080, lng : -0.1456320 },
···
{ lat : 51.5061590, lng : -0.140280 },
{ lat : 51.5047420, lng : -0.1470490 },
{ lat : 51.5126760, lng : -0.1189760 },
{ lat : 51.5108480, lng : -0.1208480 }
];
function initMap() {
var mapOptions = {};
mapOptions.center = {lat: 48.856613, lng: 2.352222};
mapOptions.zoom = 3;
map = new HWMapJsSDK.HWMap(document.getElementById('map'), mapOptions);
generateMarkers(locations);
markerCluster = new HWMapJsSDK.HWMarkerCluster(map, markers);
}
function generateMarkers(locations) {
for (let i = 0; i < locations.length; i++) {
var opts = {
position: locations[i]
};
markers.push(new HWMapJsSDK.HWMarker(opts));
}
}
Cluster markers Result:
Information Window:
The HMS Core Map SDK supports the display of information windows on the map. There are two types of information windows: One is to display text or image independently, and the other is to display text or image in a popup above a marker. The information window provides details about a marker.
Code:
var infoWindow;
function initMap() {
var mapOptions = {};
mapOptions.center = {lat: 48.856613, lng: 2.352222};
mapOptions.zoom = 8;
var map = new HWMapJsSDK.HWMap(document.getElementById('map'), mapOptions);
infoWindow = new HWMapJsSDK.HWInfoWindow({
map,
position: {lat: 48.856613, lng: 2.352222},
content: 'This is to show mouse event of another marker',
offset: [0, -40],
});
}
Info window Result:
Ground Overlay
The builder function of GroundOverlay uses the URL, LatLngBounds, and GroundOverlayOptions of an image as the parameters to display the image in a specified area on the map. The sample code is as follows:
Code:
var map;
var mGroundOverlay;
function initMap() {
var mapOptions = {};
mapOptions.center = {lat: 48.856613, lng: 2.352222};
mapOptions.zoom = 8;
map = new HWMapJsSDK.HWMap(document.getElementById('map'), mapOptions);
var imageBounds = {
north: 49,
south: 48.5,
east: 2.5,
west: 1.5,
};
mGroundOverlay = new HWMapJsSDK.HWGroundOverlay(
// Path to a local image or URL of an image.
'huawei_logo.png',
imageBounds,
{
map: map,
opacity: 1,
zIndex: 1
}
);
}
Marker Result:
Ionic / Cordova Map Implementation
In this part of article we are supposed to add Huawei Map Javascript API’s.
Update Index.html to implment Huawei Map JS scripts:
You need to update src/index.html and include Huawei map javacript cloud script url.
Code:
function loadMapScript() {
const apiKey = encodeURIComponent(
"API_KEY"
);
const src = `https://mapapi.cloud.huawei.com/mapjs/v1/api/js?callback=initMap&key=${apiKey}`;
const mapScript = document.createElement("script");
mapScript.setAttribute("src", src);
document.head.appendChild(mapScript);
}
function initMap() { }
loadMapScript();
Make new Map page:
Code:
ionic g page maps
Update maps.page.ts file and update typescript:
Code:
import { Component, OnInit, ChangeDetectorRef } from "@angular/core";
import { Observable } from "rxjs";
declare var HWMapJsSDK: any;
declare var cordova: any;
@Component({
selector: "app-maps",
templateUrl: "./maps.page.html",
styleUrls: ["./maps.page.scss"],
})
export class MapsPage implements OnInit {
map: any;
baseLat = 24.713552;
baseLng = 46.675297;
ngOnInit() {
this.showMap(his.baseLat, this.baseLng);
}
ionViewWillEnter() {
}
ionViewDidEnter() {
}
showMap(lat = this.baseLat, lng = this.baseLng) {
const mapOptions: any = {};
mapOptions.center = { lat: lat, lng: lng };
mapOptions.zoom = 10;
mapOptions.language = "ENG";
this.map = new HWMapJsSDK.HWMap(document.getElementById("map"), mapOptions);
this.map.setCenter({ lat: lat, lng: lng });
}
}
Ionic / Cordova App Result:
Native Application Huawei JS API Implementation
In this part of article we are supposed to add javascript based Huawei Map html version into our Native through webview. This part of implementation will be helpful for developer who required very minimal implementation of map.
Make assets/www/map.html file
Add the following HTML code inside map.html file:
Code:
var map;
var mMarker;
var infoWindow;
function initMap() {
const LatLng = { lat: 24.713552, lng: 46.675297 };
const mapOptions = {};
mapOptions.center = LatLng;
mapOptions.zoom = 10;
mapOptions.scaleControl = true;
mapOptions.locationControl= true;
mapOptions.language = "ENG";
map = new HWMapJsSDK.HWMap(
document.getElementById("map"),
mapOptions
);
map.setCenter(LatLng);
mMarker = new HWMapJsSDK.HWMarker({
map: map,
position: LatLng,
zIndex: 10,
label: 'A',
icon: {
opacity: 0.5
}
});
mMarker.addListener('click', () => {
infoWindow.open();
});
infoWindow = new HWMapJsSDK.HWInfoWindow({
map,
position: LatLng,
content: 'This is to info window of marker',
offset: [0, -40],
});
infoWindow.close();
}
Add the webview in your layout:
Code:
< WebView
android:id="@+id/webView_map"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
Update your Activity class to call html file
Code:
class MainActivity : AppCompatActivity() {
lateinit var context: Context
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
context = this
val mWebview = findViewById(R.id.webView_map)
mWebview.webChromeClient = WebChromeClient()
mWebview.webViewClient = WebViewClient()
mWebview.settings.javaScriptEnabled = true
mWebview.settings.setAppCacheEnabled(true)
mWebview.settings.mediaPlaybackRequiresUserGesture = true
mWebview.settings.domStorageEnabled = true
mWebview.loadUrl("file:///android_asset/www/map.html")
}
}
Internet permission:
Don’t forget to add internet permissions in androidmanifest.xml file.
Code:
< uses-permission android:name="android.permission.INTERNET" />
< uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
< uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
Native app Result:
References:
Huawei Map JavaScript API:
https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides/javascript-api-introduction-0000001050164063
Complete Ionic JS Map Project:
https://github.com/salmanyaqoob/Ionic-All-HMS-Kits
Conclusion
Huawei Map JavaSript Api will be helpful for JavaScript developers to implement Huawei Map on cross platforms like “Cordova, Ionic, React-Native” and also helpful for the Native developers to implement under his projects. Developers can also able to implement Huawei Maps on websites.
Thank you very much, very helpful.
More information like this, you can visit HUAWEI Developer Forum
Introduction
Flight booking app allows user to search and book flight. In this article, we will integrate app messaging and analytics into demo flight booking application.
For prerequisite and set up, refer to part 1.
Usecase
We will show the marker and dotted polyline between source and destination airport using Huawei Map kit.
Huawei Map Kit
1. Enable Map kit in AGC. Refer Service Enabling.
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
2. In order to use Huawei map kit, we need to integrate following code in app-level build.gradle.
Code:
implementation 'com.huawei.hms:maps:5.0.1.300'
3. Add following permissions in Manifest.
Code:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="com.huawei.appmarket.service.commondata.permission.GET_COMMON_DATA"/>
//To obtain current device location
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
4. To provide real time permission.
Code:
private void requestPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Log.i(TAG, "sdk >= 23 M");
if (ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
|| ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
String[] strings =
{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION};
ActivityCompat.requestPermissions(this, strings, 1);
}
}
}
5. Add following code to in your xml file to set the map attributes.
Code:
<com.huawei.hms.maps.MapView
android:id="@+id/mapview_huawei"
android:layout_width="match_parent"
android:layout_height="200dp" />
6. To use map in your app, you need to implement OnMapReadyCallback.
Code:
public class PolylineholderFragment extends Fragment implements OnMapReadyCallback {
@Override
public void onMapReady(HuaweiMap huaweiMap) {
}
}
7. Load MapView in the onCreate() or onCreateView() method and call getMapAsync() to register the callback.
Code:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_flight_details);
mMapView = findViewById(R.id.mapview_huawei);
Bundle mapViewBundle = null;
if (savedInstanceState != null) {
mapViewBundle = savedInstanceState.getBundle("MapViewBundleKey");
}
mMapView.onCreate(mapViewBundle);
mMapView.getMapAsync(this);
}
8. To draw marker at source and destination airport.
Code:
LatLng origin = new LatLng(HelperUtilities.originAirport.getLatitude(), HelperUtilities.originAirport.getLongitude());
LatLng destination = new LatLng(HelperUtilities.destinationAirport.getLatitude(), HelperUtilities.destinationAirport.getLongitude());
huaweiMap.addMarker(new MarkerOptions().position(origin).icon(BitmapDescriptorFactory.fromResource(R.drawable.htl_location_accent)));
huaweiMap.addMarker(new MarkerOptions().position(destination).icon(BitmapDescriptorFactory.fromResource(R.drawable.htl_location_accent)));
9. To specify the camera position of specific location.
Code:
CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLng(origin);
huaweiMap.moveCamera(cameraUpdate);
10. To draw dotted polyline between origin and destination airport, we need to create a list of PatternItem and call method addPoyline() and pass the source and destination LatLng object.
Code:
int PATTERN_GAP_LENGTH_PX = 2;
final PatternItem DOT = new Dot();
PatternItem GAP = new Gap(PATTERN_GAP_LENGTH_PX);
int PATTERN_DASH_LENGTH_PX = 2;
PatternItem DASH = new Dash(PATTERN_DASH_LENGTH_PX);
List<PatternItem> PATTERN_POLYGON_BETA =
Arrays.asList(DOT, GAP, DASH, GAP);
mPolyline = huaweiMap.addPolyline(new PolylineOptions()
.add(origin, destination)
.color(Color.RED).pattern(PATTERN_POLYGON_BETA)
.width(2));
11. OnMapReady() imlementation looks like this.
Code:
int PATTERN_GAP_LENGTH_PX = 2;
int PATTERN_DASH_LENGTH_PX = 2;
@Override
public void onMapReady(HuaweiMap huaweiMap) {
if (null == huaweiMap) {
return;
}
if (null != mPolyline) {
mPolyline.remove();
mPolyline = null;
}
final PatternItem DOT = new Dot();
PatternItem GAP = new Gap(PATTERN_GAP_LENGTH_PX);
PatternItem DASH = new Dash(PATTERN_DASH_LENGTH_PX);
List<PatternItem> PATTERN_POLYGON_BETA =
Arrays.asList(DOT, GAP, DASH, GAP);
LatLng origin = new LatLng(HelperUtilities.originAirport.getLatitude(), HelperUtilities.originAirport.getLongitude());
LatLng destination = new LatLng(HelperUtilities.originAirport.getLatitude(), HelperUtilities.destinationAirport.getLongitude());
mPolyline = huaweiMap.addPolyline(new PolylineOptions()
.add(origin, destination)
.color(Color.RED).pattern(PATTERN_POLYGON_BETA)
.width(2));
CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLng(origin);
huaweiMap.moveCamera(cameraUpdate);
huaweiMap.addMarker(new MarkerOptions().position(origin).icon(BitmapDescriptorFactory.fromResource(R.drawable.htl_location_accent)));
huaweiMap.addMarker(new MarkerOptions().position(destination).icon(BitmapDescriptorFactory.fromResource(R.drawable.htl_location_accent)));
}
Conclusion
In this article, we have shown how Huawei Map kit can be used to draw dotted polyline between origin and destination airport in flight detail screen.
References
For detailed guide, refer to this link.
For our App, users will be able to send messages to others if they know the nickname of the destination user. In this part we will allow the user to choose or edit his nickname, and add the business logic of the user management on our backend.
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Checking the user's nickname
Upon the startup, if the user is already registered, the app will check if the user has already registered his nickname. To achieve this, we will use our REST client built with HQUIC.
Note: The next class is based on my previous article about Building an Android QUIC REST Client with HQUIC [kotlin], so is highly recomendable to read it first.
Code:
class HQUICClient(context: Context, private val requestId:Int=0) : UrlRequest.Callback() {
var hquicService: HQUICService? = null
val CAPACITY = 10240
val TAG="QUICClient"
val response=ByteArrayOutputStream()
var listener: HQUICClientListener?=null
init {
hquicService = HQUICService(context)
hquicService?.setCallback(this)
}
fun makeRequest(url: String, method: String, headers: HashMap<String, String>?=null,body:ByteArray?=null){
hquicService?.sendRequest(url,method,headers,body)
}
override fun onRedirectReceived(
request: UrlRequest?,
info: UrlResponseInfo?,
newLocationUrl: String?
) {
request?.followRedirect()
}
override fun onResponseStarted(request: UrlRequest?, info: UrlResponseInfo?) {
val byteBuffer = ByteBuffer.allocateDirect(CAPACITY)
request?.read(byteBuffer)
}
override fun onReadCompleted(
request: UrlRequest?,
info: UrlResponseInfo?,
byteBuffer: ByteBuffer?
) {
byteBuffer?.apply {
response.write(array(),arrayOffset(),position())
response.flush()
}
request?.read(ByteBuffer.allocateDirect(CAPACITY))
}
override fun onSucceeded(request: UrlRequest?, info: UrlResponseInfo?) {
listener?.onSuccess(requestId,response.toByteArray())
}
override fun onFailed(request: UrlRequest?, info: UrlResponseInfo?, error: CronetException?) {
listener?.apply { onFailure(requestId,error.toString()) }
}
interface HQUICClientListener{
fun onSuccess(requestId: Int,response: ByteArray)
fun onFailure(requestId: Int,error: String)
}
}
Now, we will create a ProfileUtils class which will be responsible for the related nickname operations.
Code:
class ProfileUtils(private val callback:ProfileCallback) :HQUICClient.HQUICClientListener{
companion object {
const val TAG="ProfileUtils"
const val PROFILE_URL = "https://qz41hkiav2.execute-api.us-east-2.amazonaws.com/Prod/profile/"
const val NICKNAME_RESOURCE = "nickname/"
const val UID = "uid"
const val NICKNAME="nickname"
const val CHECK_NICKNAME=200
const val UPDATE_NICKNAME=300
}
fun checkNickname(context: Context,uid: String) {
Log.e(TAG,"Checking Nickname")
publishUid(context,uid)
val apiURL = "$PROFILE_URL?$UID=$uid"
CoroutineScope(Dispatchers.IO).launch {
context.let {
HQUICClient(it, CHECK_NICKNAME).apply{
[email protected]
makeRequest(apiURL,"GET")
}
}
}
}
private fun publishUid(context: Context, uid: String) {
val intent= Intent().apply {
action = AccountPushReceiver.ACTION
putExtra(AccountPushReceiver.PARAM,AccountPushReceiver.UID)
putExtra(AccountPushReceiver.UID,uid)
}
context.sendBroadcast(intent)
}
fun updateNickname(context:Context,uid:String,nickname:String){
Log.e(TAG,"Updating Nickname")
//publishUid(context,uid)
val url="$PROFILE_URL$NICKNAME_RESOURCE"
val params=JSONObject().apply {
put(UID,uid)
put(NICKNAME,nickname)
}
val headers=HashMap<String,String>()
headers["Content-Type"]= "application/json"
CoroutineScope(Dispatchers.IO).launch {
context.let {
HQUICClient(it, UPDATE_NICKNAME).apply{
[email protected]
makeRequest(url,"POST",headers,params.toString().toByteArray())
}
}
}
}
override fun onSuccess(requestId:Int,response: ByteArray) {
val result= String(response)
Log.e(TAG,"RequestID: $requestId $result")
val json=JSONObject(result)
when(requestId){
CHECK_NICKNAME ->handleNicknameCheck(json)
UPDATE_NICKNAME ->handleNicknameUpdate(json)
}
}
private fun handleNicknameCheck(json:JSONObject){
val body=json.getJSONObject("body")
if(body.has("nickname")){
val nickname=body.getString("nickname")
callback.onNicknameResult(ProfileCallback.NICKNAME_RETRIEVED,nickname)
}else callback.onNicknameResult(ProfileCallback.NICKNAME_EMPTY,"")
}
private fun handleNicknameUpdate(json:JSONObject){
val body=json.getJSONObject("body")
when(json.getInt("responseCode")){
ProfileCallback.NICKNAME_USED ->{
callback.onNicknameResult(ProfileCallback.NICKNAME_USED,body.getString("message"))
}
ProfileCallback.NICKNAME_INSERTED->{
callback.onNicknameResult(ProfileCallback.NICKNAME_USED,body.getString("nickname"))
}
}
}
override fun onFailure(requestId:Int,error: String) {
}
interface ProfileCallback{
companion object{
const val NICKNAME_USED=1
const val NICKNAME_INSERTED=2
const val NICKNAME_RETRIEVED=3
const val NICKNAME_EMPTY=4
}
fun onNicknameResult(resultCode:Int,data: String)
}
}
As you can see, the HQUICClient receives 2 parameters, the first is the ClientListener which will listen for the response. The second parameter is the request id, as we will use the same listener lo listen the result of different operations, the request id will helps us to know which operation is giving us the reported response.
Server side configuration
As you may remember, we are using AWS to manage our backend, the services used are:
API Gateway: To build our REST API
Lambda: To perform the backend operations
DynamoDB: To store the users data
First, lets check the DynamoDB table structure
We have 3 fields
uid: The user id provided by Auth Service
token: The push token provided by the Huawei Push Kit
nickname: The unique nickname choosen by the user, this value will be shared with other users in order to send and receive messages.
Registering the user
The push token may change with the time or if the user clears the app date, so it must be reported upon each app startup. The lambda function to handle this operation is the next:
Code:
'use strict'
const AWS = require('aws-sdk');
AWS.config.update({ region: "us-east-2" });
exports.handler = async (event) => {
// TODO implement
console.log(event);
const ddb = new AWS.DynamoDB({ apiVersion: "2012-08-10" });
const documentClient = new AWS.DynamoDB.DocumentClient({ region: "us-east-2" });
const response = {
statusCode: 200,
body: JSON.stringify('InsertSuccesful'),
};
const result=await writeDB(event,documentClient).catch(function(){
response.statusCode=400;
response.body="Insert"
});
return response;
};
function writeDB(event, documentClient){
const entry = {
TableName: "MessengerUsers",
Item: {
uid: event.uid
}
};
if(event.token){
entry.Item.token=event.token;
}
console.log(JSON.stringify(entry));
return documentClient.put(entry).promise();
}
Note: The function above must be modified to update the token if the uid is already registered. For now it will overwrite the existin registry with the given values.
Querying the user's data
The app will ask if there is an already registered nickname for the given UID, this operation will be performed by sending a GET request to our API. The function responsible for that is the next:
Code:
'use strict'
const AWS = require('aws-sdk');
AWS.config.update({ region: "us-east-2" });
exports.handler = async (event) => {
console.log(JSON.stringify(event));
const ddb = new AWS.DynamoDB({ apiVersion: "2012-08-10" });
const documentClient = new AWS.DynamoDB.DocumentClient({ region: "us-east-2" });
var params = {
Key: {
"uid": event.uid
},
TableName: "MessengerUsers"
};
// TODO implement
const response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
var data;
try{
data=await documentClient.get(params).promise();
console.log("Query succeeded.");
response.body=data.Item;
}catch(err){
console.error("Unable to query. Error:", JSON.stringify(err, null, 2));
response.body=JSON.stringify(err, null, 2);
}
return response;
};
Updating the Nickname
When the user enters his nickname, we must validate first if the nickname is available for using, if is it, we will update the user registry with the new nickname. If not, we must notify the user to choose another one.
Code:
'use strict'
const AWS = require('aws-sdk');
AWS.config.update({ region: "us-east-2" });
exports.handler = async(event) => {
console.log(JSON.stringify(event));
const ddb = new AWS.DynamoDB({ apiVersion: "2012-08-10" });
const documentClient = new AWS.DynamoDB.DocumentClient({ region: "us-east-2" });
var data;
try{
data =await checkNickname(documentClient, event.nickname);
}catch(err){
return response(400,err);
}
if(data.Count>0){
return response(1,{message:event.nickname+" is already used"});
}
try {
const result=await update(documentClient,event.nickname,event.uid);
return response(2,result.Attributes);
} catch (e) {
return response(400,e);
}
};
function response(code, data){
return{
responseCode:code,
body:data
}
}
const checkNickname =(documentClient, nickname)=> {
var params = {
FilterExpression: 'nickname = :hkey',
ExpressionAttributeValues: {
':hkey': nickname
},
TableName: "MessengerUsers"
};
return documentClient.scan(params).promise();
};
const update=(documentClient,nickname,uid)=>{
const entry = {
TableName: "MessengerUsers",
Key:{
uid:uid
},
UpdateExpression: "set nickname = :nickname",
ExpressionAttributeValues:{
":nickname":nickname,
},
ReturnValues:"UPDATED_NEW"
};
return documentClient.update(entry).promise();
};
More details, you can visit https://forums.developer.huawei.com/forumPortal/en/topic/0204400884797260110
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Hello friends and welcome back to my series of integrating various Huawei services. In this article I will show the integration of Geocoding API using Retrofit to get the coordinates from a format address followed by the integration of Directions API where we input the aforementioned coordinates to get the directions and steps from origin to destination together with the distance and time calculation.
Introduction
As explained in the previous section, we have to perform various API requests and integrate them using Retrofit. We will take them step by step, starting from explaining these services and how we use them. To start your app development in Huawei, you first have to perform some configurations needed to use the Kits and Services it provides, by following this post.
Geocoding APIGeocoding API is a service providing two main functionalities:
Forward Geocoding: a service that enables the retrieval of spatial coordinates (latitude, longitude) from a structured address. It can return up to 10 results for any given address, ranking them according to importance and accuracy.
Reverse Geocoding: does the opposite of forward geocoding by providing formatted addresses when given a set of coordinates. This service can return up to 11 formatted addresses for the coordinates given, again according to importance and accuracy.
For the purpose of this article, we will be using Forward Geocoding to retrieve the coordinates of a site based on the formatted address.
Integration
The first thing we need to do after performing the necessary configurations, would be to add the dependencies in the app-level build gradle.
Code:
//Retrofit
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation("com.squareup.okhttp3:logging-interceptor:4.2.2")
After that we will set our Geocoding Retrofit Requests and Response data classes to determine what we need to send as a parameter and retrieve as a response.
Code:
data class GeocodingRequest(
u/SerializedName("address") val address: String?,
u/SerializedName("language") val language: String?
)
data class Location(
u/SerializedName("lng") val lng: Double?,
u/SerializedName("lat") val lat: Double?
)
data class GeocodingResponse(
u/SerializedName("returnCode") val returnCode: String? ,
u/SerializedName("sites") val sites: Array<Sites>?,
u/SerializedName("returnDesc") val returnDesc: String?
)
data class Sites(
u/SerializedName("formatAddress") val formatAddress: String?,
u/SerializedName("location") val location: Location?
)
You can determine the request and response parameters based on the rules of the API requests and our needs.
After setting the data classes, we will need to establish a Retrofit client that will serve as an authenticator and interactor with the API and send network requests.
Code:
class GeocodeRetrofit {
val BASE_URL_DIRECTIONS = "https://siteapi.cloud.huawei.com/mapApi/v1/siteService/"
private val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(BASE_URL_DIRECTIONS)
.client(setInterceptors())
.addConverterFactory(GsonConverterFactory.create())
.build()
fun <S> createService(serviceClass: Class<S>?): S {
return retrofit.create(serviceClass)
}
private fun setInterceptors() : okhttp3.OkHttpClient {
val logger = HttpLoggingInterceptor()
logger.level = HttpLoggingInterceptor.Level.BODY
return okhttp3.OkHttpClient.Builder()
.readTimeout(60, TimeUnit.SECONDS)
.connectTimeout(60, TimeUnit.SECONDS)
.addInterceptor { chain ->
val url: okhttp3.HttpUrl = chain.request().url.newBuilder()
.addQueryParameter("key", API_KEY)
.build()
val request = chain.request().newBuilder()
.header("Content-Type", "application/json")
.url(url)
.build()
chain.proceed(request)
}
.addInterceptor(logger)
.build()
}
}
The base URL is given as below, and we should keep in mind to add the API Key of the agconnect-services.json file.
val BASE_URL_DIRECTIONS = "https://siteapi.cloud.huawei.com/mapApi/v1/siteService/"
The next step would be to create a repo;
Code:
class GeocodingBaseRepo {
private var geocodingApis : GeocodingInterface? = null
fun getInstance(): GeocodingInterface?{
if(geocodingApis==null)
setMainApis()
return geocodingApis
}
private fun setMainApis(){
geocodingApis = GeocodeRetrofit().createService(GeocodingInterface::class.java)
}
}
We proceed by creating an interface that will serve as exactly that, an interface between the API and the Retrofit Client.
Code:
interface GeocodingInterface {
@Headers("Content-Type: application/json; charset=UTF-8")
@POST("geocode")
fun listPost (
@Body geocodingRequest: GeocodingRequest
): Call<GeocodingResponse>
}
Once we have stablished all of the above, we can finally request the API in our activity or fragment. To adapt it to our case, we have created to editable text fields where user can insert origin and destination addresses. Based on that we make two geocode API calls, for origin and destination respectively, and observe their results through callbacks.
Code:
fun performGeocoding(type: String, geocodingRequest: GeocodingRequest, callback: (ResultData<GeocodingResponse>) -> Unit){
GeocodingBaseRepo().getInstance()?.listPost(geocodingRequest)?.enqueue(
object : Callback<GeocodingResponse> {
override fun onFailure(call: Call<GeocodingResponse>, t: Throwable) {
Log.d(TAG, "ERROR GEOCODING" + t.message)
}
override fun onResponse(
call: Call<GeocodingResponse>,
response: Response<GeocodingResponse>
) {
if (response.isSuccessful) {
Log.d(TAG, "SUCCESS GEOCODING" + response.message())
response.body()?.let {
if(type == "parting"){
callback.invoke(ResultData.Success(response.body()))
}
if(type == "destination"){
callback.invoke(ResultData.Success(response.body()))
}
}
}
}
})
}
private fun callOriginData(){
geocodingRequest = GeocodingRequest(partingaddress, "EN")
performGeocoding("parting" ,geocodingRequest, callback = {
it.handleSuccess {
var startingLatitude = it.data?.sites?.get(0)?.location?.lat
var startingLongitude = it.data?.sites?.get(0)?.location?.lng
origin = startingLatitude?.let { it1 -> startingLongitude?.let { it2 ->
LatLngData(it1,
it2
)
} }!!
callDestinationData()
}
})
}
private fun callDestinationData(){
geocodingRequest = GeocodingRequest(destinationaddress, "EN")
performGeocoding("destination", geocodingRequest, {
it.handleSuccess {
var endingLatitude = it.data?.sites?.get(0)?.location?.lat
var endingLongitude = it.data?.sites?.get(0)?.location?.lng
destination = endingLatitude?.let { it1 -> endingLongitude?.let { it2 ->
LatLngData(it1,
it2
)
} }!!
callDirections()
}
})
}
Geocoding Origin Results
Geocoding Destination Results
Directions APIDirections API is a Huawei service that provides three main functionalities:
Walking Route Planning: Plans an available walking route between two points within 150 km.
Cycling Route Planning: Plans an available cycling route between two points within 500 km.
Driving Route Planning: Plans an available driving route between two points.
IntegrationAfter being done with Geocoding, we need to use the results data from it and insert it into Directions API requests to be able to get all three route planning available between origin and destination coordinates. Similar to Geocode, we first establish the request and response data classes.
Code:
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 )
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: Double,
@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)
We then create a Retrofit Client for Directions API.
Code:
class DirectionsRetrofit {
val BASE_URL_DIRECTIONS = "https://mapapi.cloud.huawei.com/mapApi/v1/"
private val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(BASE_URL_DIRECTIONS)
.client(setInterceptors())
.addConverterFactory(GsonConverterFactory.create())
.build()
fun <S> createService(serviceClass: Class<S>?): S {
return retrofit.create(serviceClass)
}
private fun setInterceptors() : okhttp3.OkHttpClient {
val logger = HttpLoggingInterceptor()
logger.level = HttpLoggingInterceptor.Level.BODY
return okhttp3.OkHttpClient.Builder()
.readTimeout(60, TimeUnit.SECONDS)
.connectTimeout(60, TimeUnit.SECONDS)
.addInterceptor { chain ->
val url: okhttp3.HttpUrl = chain.request().url.newBuilder()
.addQueryParameter("key", API_KEY)
.build()
val request = chain.request().newBuilder()
.header("Content-Type", "application/json")
.url(url)
.build()
chain.proceed(request)
}
.addInterceptor(logger)
.build()
}
}
In this case, what will serve as our Base URL will be the URL below:
val BASE_URL_DIRECTIONS = "https://mapapi.cloud.huawei.com/mapApi/v1/"
We create a repo once again;
Code:
open class DirectionsBaseRepo {
private var directionsApis : DirectionsInterface? = null
fun getInstance(): DirectionsInterface?{
if(directionsApis==null)
setMainApis()
return directionsApis
}
private fun setMainApis(){
directionsApis = DirectionsRetrofit().createService(DirectionsInterface::class.java)
}
}
And similarly to the previous process we followed in Geocode we need an interface:
Code:
interface DirectionsInterface {
@POST("routeService/{type}")
fun getDirectionsWithType(
@Path(value = "type",encoded = true) type : String,
@Body directionRequest: DirectionsRequest
): Call<DirectionsResponse>
}
The only part that is extra from the previous API request is that we need an enumerating class to store the different direction types which will be determined from the user.
Code:
enum class DirectionType(val type: String) {
WALKING("walking"),
BICYCLING("bicycling"),
DRIVING("driving")
}
The only thing left for us to do now is to make the API call within the activity / fragment.
For this part we have created three image buttons for three direction types, and we call the direction API based on the type users selected. Basically if user wants to see the driving route, they select the driving type and a Direction API request with type driving is made.
Code:
fun getDirections(type: String, directionRequest: DirectionsRequest, callback: (ResultData<DirectionsResponse>) -> Unit){
DirectionsBaseRepo().getInstance()?.getDirectionsWithType(type,directionRequest)?.enqueue(object : Callback<DirectionsResponse>{
override fun onFailure(call: Call<DirectionsResponse>, t: Throwable) {
Log.d(TAG, "ERROR DIRECTIONS" + t.message)
}
override fun onResponse(call: Call<DirectionsResponse>, response: Response<DirectionsResponse>) {
Log.d(TAG, "success DIRECTIONS" + response.message())
if(response.isSuccessful){
response.body()?.let {
callback.invoke(ResultData.Success(it))
}
}
}
})
}
Code:
getDirections(DirectionType.DRIVING.type, directionRequest, {
it.handleSuccess {
it.data?.routes?.get(0)?.paths?.get(0)?.steps?.get(0)?.startLocation?.lat?.let { it1 ->
it.data?.routes?.get(0)?.paths?.get(0)?.steps?.get(0)?.startLocation?.lng?.let { it2 ->
commonMap.animateCamera(
it1, it2, 10f )
}
}
it.data?.routes?.get(0)?.paths?.get(0)?.steps?.forEach {
it.polyline.forEach{
commonPolylineCoordinates.add(CommonLatLng(it.lat, it.lng))
}
}
drawPolyline(commonPolylineCoordinates)
carDistance = it.data?.routes?.get(0)?.paths?.get(0)?.distanceText.toString()
binding.distanceByCar.setText(carDistance)
carTime = it.data?.routes?.get(0)?.paths?.get(0)?.durationText.toString()
binding.timebyCar.setText(carTime)
}
})
As a result you can make use of all the response fields, including the steps needed to reach a place, the distance and time, or take the polyline coordinates and draw a route on the map. For this project I have decided to draw the route on the map and calculate the time and distance between the coordinates.
The final result is displayed below:
Geocoding API + Directions API Results
Geocoding API + Directions API Results
Tips and Tricks
It is a little tricky to work with asynchronous data since you never know when they will return their responses. We need to call geocode APIs for both origin and destination, and we want to make sure that the destination is called after the origin. To perform this you can call the destination geocoding API in the handle success part of the origin geocoding API, this way you make sure when you get a destination, you will definitely have an origin.
Similarly, you want to call the directions API when you have both origin and destination coordinates, hence you can call it in the handle success part of the destination geocoding call. This way you can be sure directions API call will not have empty or static coordinates.
Be careful to clean the polyline after switching between navigation types.
ConclusionIn this article, we talked about the integration of Geocoding API and performing Forward Geocoding to get the coordinates of a place of origin and destination, based on the formatted addresses. We proceeded by retrieving the origin and destination coordinates and ‘feeding’ them to the Directions API requests to get the route planning for navigation types of driving, cycling and walking. Afterwards we get the response of the Directions API call and use the result data as needed from our use cases. In my case I used the polyline data to draw on the map, and display the distance and time from two places. I hope you give it a shot, let me know what you think. Stay healthy and happy, see you in other articles.
Referencehttps://developer.huawei.com/consum...iretions-api-introduction-0000001050178120-V5
https://developer.huawei.com/consum...ces-V5/webapi-forward-geo-0000001050163921-V5
https://github.com/HMS-Core/hms-mapkit-demo
What are the required permission?