先上一張效果圖
一.在高德控制臺創(chuàng)建應(yīng)用獲取key
這一步比較簡單,沒什么可說的吃既。如何申請 Key
二.接入工程
1.通過Android Studio引入相關(guān)的包,官方文檔說的比較明確跨细,這里不再闡述鹦倚,附上相關(guān)的鏈接:Android Studio 配置工程。
2.配置清單文件冀惭,官網(wǎng)文檔這里沒有細(xì)說震叙,具體可以查看示例代碼中的配置,這里直接貼出相關(guān)配置散休。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.gfd.demo">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!--用于訪問GPS定位-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<!--用于獲取運營商信息媒楼,用于支持提供運營商信息相關(guān)的接口-->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<!--用于訪問wifi網(wǎng)絡(luò)信息,wifi信息會用于進行網(wǎng)絡(luò)定位-->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<!--用于獲取wifi的獲取權(quán)限戚丸,wifi信息會用來進行網(wǎng)絡(luò)定位-->
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<!--用于訪問網(wǎng)絡(luò)划址,網(wǎng)絡(luò)定位需要上網(wǎng)-->
<uses-permission android:name="android.permission.INTERNET"/>
<!--用于讀取手機當(dāng)前的狀態(tài)-->
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<!--用于寫入緩存數(shù)據(jù)到擴展存儲卡-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!--用于申請調(diào)用A-GPS模塊-->
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
<!--用于申請獲取藍牙信息進行室內(nèi)定位-->
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<application
android:name=".app.AppApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:resizeableActivity="true"
android:maxAspectRatio="2.4"
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
<!--高德地圖配置-->
<service android:name="com.amap.api.location.APSService"/>
<service android:name="com.amap.api.track.AMapTrackService"/>
<meta-data
android:name="com.amap.api.v2.apikey"
android:value="請輸入您的用戶Key"/>
</application>
</manifest>
注意:Android6.0的動態(tài)權(quán)限申請。
三.獲取Service ID
- Service
即獵鷹軌跡服務(wù)限府。一個service對應(yīng)一個軌跡管理系統(tǒng)夺颤,通過一個service可管理多個終端設(shè)備(即terminal),service的唯一標(biāo)識符是sid胁勺。舉例說明拂共,針對某網(wǎng)約車公司的軌跡管理系統(tǒng),可以創(chuàng)建一個service姻几,在創(chuàng)建的service中管理所有營運車輛的軌跡宜狐。
- Terminal
一個terminal代表現(xiàn)實中一個終端設(shè)備,它可以是個體蛇捌、也可以是一輛車或者任何運動的物體抚恒。在同一個service中,terminal以
tid
作為唯一標(biāo)識络拌。
要使用軌跡服務(wù)俭驮,必須先創(chuàng)建一個service,通過請求相應(yīng)的接口來獲取servie id
春贸。
fun getTrackServiceId(name: String) {
OkGo.post<String>("https://tsapi.amap.com/v1/track/service/add")
.params("key", "2451a7584eb9aee041e0439bd3cdd51e")
.params("name", name)
.execute(object : StringCallback() {
override fun onSuccess(response: Response<String>?) {
val json = response?.body().toString()
Logger.e("服務(wù)id:$json")
val data = Gson().fromJson(json, TrackServiceEntity::class.java)
AccountConfig.SERVICE_ID = data.data.sid.toLong()
}
})
}
有關(guān)接口的參數(shù)和返回值的詳細(xì)說明見:接口說明混萝。
這里重點說一下參數(shù)key這個參數(shù)值。它是用戶在高德地圖官網(wǎng)申請Web服務(wù)API類型KEY萍恕,官網(wǎng)文檔在這說的不是很詳細(xì)逸嘀。我們怎么獲取這個key呢?在我們上面創(chuàng)建的應(yīng)用下允粤,去添加一個web類型的key:
輸入自定義的名字后提交即可獲取web類型的key:
這一步獲取的key就是就是上面請求中key對應(yīng)的值崭倘。
注意:service id 只需要獲取一次翼岁,不需要每次使用的時候都去請求獲取,除非添加新的service司光。(每個 Key 下最多注冊15個 Service)
通過以上步驟琅坡,軌跡相關(guān)的配置已經(jīng)完成,下面就具體看一下代碼的實現(xiàn)残家。
四.相關(guān)代碼
使用軌跡服務(wù)榆俺,首先需要初始化獵鷹sdk服務(wù)類以及一些參數(shù)的配置:
fun init(context: Context) {
aMapTrackClient = AMapTrackClient(context)
//將定位信息采集周期設(shè)置為2s,上報周期設(shè)置為20s坞淮,注意定位信息采集周期的范圍應(yīng)該是1s~60s谴仙,上報周期的范圍是采集周期的5~50倍。
aMapTrackClient.setInterval(2, 20)
//設(shè)置軌跡點緩存大小為20M,最大為50M,默認(rèn)值為50
aMapTrackClient.setCacheSize(20)
//設(shè)置定位模式碾盐,默認(rèn)為高精度
aMapTrackClient.setLocationMode(LocationMode.HIGHT_ACCURACY)
}
定位模式:
- HIGHT_ACCURACY(高精度定位模式):
在這種定位模式下晃跺,將同時使用高德網(wǎng)絡(luò)定位和衛(wèi)星定位,優(yōu)先返回精度高的定位
- BATTERY_SAVING(低功耗定位模式):
在這種模式下,將只使用高德網(wǎng)絡(luò)定位毫玖。使用網(wǎng)絡(luò)定位就是使用基站定位掀虎。
- DEVICE_SENSORS(僅設(shè)備定位模式):
在這種模式下,將只使用衛(wèi)星定位付枫。
其次需要創(chuàng)建用于接收獵鷹sdk服務(wù)啟停狀態(tài)的監(jiān)聽器:
//SimpleOnTrackLifecycleListener是實現(xiàn)接口OnTrackLifecycleListener的一個類烹玉,我們繼承它只需要重寫需要的方法即可。
onTrackLifecycleListener = object : SimpleOnTrackLifecycleListener() {
override fun onBindServiceCallback(status: Int, msg: String?) {
Logger.e("onBindServiceCallback, status: $status, msg: $msg")
}
//軌跡上報服務(wù)啟動回調(diào)
override fun onStartTrackCallback(status: Int, msg: String?) {
if (status == ErrorCode.TrackListen.START_TRACK_SUCEE || status == ErrorCode.TrackListen.START_TRACK_SUCEE_NO_NETWORK) {
//啟動動軌跡上報服務(wù)成功阐滩,開始軌跡采集
startGather()
isServiceRunning = true
} else if (status == ErrorCode.TrackListen.START_TRACK_ALREADY_STARTED) {
//軌跡上報服務(wù)已經(jīng)啟動二打,重復(fù)啟動
isServiceRunning = true
} else {
//"error onStartTrackCallback, status: $status, msg: $msg"
}
}
//軌跡上報服務(wù)停止回調(diào)
override fun onStopTrackCallback(status: Int, msg: String?) {
if (status == ErrorCode.TrackListen.STOP_TRACK_SUCCE) {
//停止軌跡上報服務(wù)成功
isServiceRunning = false
isGatherRunning = false
} else {
//"error onStopTrackCallback, status: $status, msg: $msg"
}
}
//定位采集開啟回調(diào)
override fun onStartGatherCallback(status: Int, msg: String?) {
when (status) {
ErrorCode.TrackListen.START_GATHER_SUCEE -> {
//定位采集開啟成功
isGatherRunning = true
}
ErrorCode.TrackListen.START_GATHER_ALREADY_STARTED -> {
//定位采集已經(jīng)開啟,重復(fù)啟動
isGatherRunning = true
}
else -> {
//"error onStartGatherCallback, status: $status, msg: $msg"
}
}
}
//停止定位采集回調(diào)
override fun onStopGatherCallback(status: Int, msg: String?) {
if (status == ErrorCode.TrackListen.STOP_GATHER_SUCCE) {
//定位采集停止成功掂榔,停止軌跡上報服務(wù)
stopTrack()
isGatherRunning = false
} else {
//"error onStopGatherCallback, status: $status, msg: $msg"
}
}
}
注意:要開啟定位采集继效,需要首先啟動軌跡上報服務(wù),等服務(wù)啟動成功后才能開啟定位采集装获。
OnTrackLifecycleListener API文檔
1.軌跡采集
軌跡采集需要提供服務(wù)id及終端id瑞信,服務(wù)id在上面通過請求相關(guān)接口已經(jīng)獲取到;終端id需要在該終端第一次使用軌跡采集的時候創(chuàng)建穴豫,所以凡简,在使用軌跡采集的時候,需要先查詢該終端id精肃,如果沒有創(chuàng)建過就創(chuàng)建一個新的秤涩,如果創(chuàng)建過就使用查詢得到的終端id。
fun startTrack() {
//查詢終端id
aMapTrackClient.queryTerminal(QueryTerminalRequest(AccountConfig.SERVICE_ID, AccountConfig.TERMINAL_NAME),
object : SimpleOnTrackListener() {
override fun onQueryTerminalCallback(queryTerminalResponse: QueryTerminalResponse?) {
if (queryTerminalResponse?.isSuccess!!) {
if (queryTerminalResponse.isTerminalExist) {
// 當(dāng)前終端已經(jīng)創(chuàng)建過司抱,直接使用查詢到的terminal id
collectionTrack(queryTerminalResponse.tid)
} else {
// 當(dāng)前終端是新終端筐眷,還未創(chuàng)建過,創(chuàng)建該終端并使用新生成的terminal id
aMapTrackClient.addTerminal(
AddTerminalRequest(
AccountConfig.TERMINAL_NAME,
AccountConfig.SERVICE_ID
), object : SimpleOnTrackListener() {
override fun onCreateTerminalCallback(addTerminalResponse: AddTerminalResponse?) {
if (addTerminalResponse?.isSuccess!!) {
collectionTrack(addTerminalResponse.tid)
}
}
})
}
} else {
//"網(wǎng)絡(luò)請求失敗状植," + queryTerminalResponse.errorMsg
}
}
})
}
獲取對應(yīng)的終端id后浊竟,開始軌跡收集:
private fun collectionTrack(terminalId: Long) {
this.terminalId = terminalId
if (uploadToTrack) {
newTrack()
} else {
appendTrack()
}
}
//不創(chuàng)建新的軌跡
private fun appendTrack() {
// 不指定track id怨喘,上報的軌跡點是該終端的散點軌跡
val trackParam = TrackParam(AccountConfig.SERVICE_ID, terminalId)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
trackParam.notification = createNotification()
}
aMapTrackClient.startTrack(trackParam, onTrackLifecycleListener)
}
//創(chuàng)建一個新的軌跡
private fun newTrack() {
aMapTrackClient.addTrack(AddTrackRequest(AccountConfig.SERVICE_ID, terminalId),
object : SimpleOnTrackListener() {
override fun onAddTrackCallback(addTrackResponse: AddTrackResponse?) {
if (addTrackResponse?.isSuccess!!) {
// trackId需要在啟動服務(wù)后設(shè)置才能生效津畸,因此這里不設(shè)置振定,而是在startGather之前設(shè)置了track id
trackId = addTrackResponse.trid
currentTrackId = trackId
Logger.e("軌跡id = $trackId")
val trackParam = TrackParam(AccountConfig.SERVICE_ID, terminalId)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
trackParam.notification = createNotification()
}
aMapTrackClient.startTrack(trackParam, onTrackLifecycleListener)
} else {
//"網(wǎng)絡(luò)請求失敗," + addTrackResponse.errorMsg
}
}
})
}
如果創(chuàng)建一個新的軌跡肉拓,那么每一個行程對應(yīng)著一條軌跡后频。
TrackParam API文檔
AddTrackRequest API文檔
2.查詢軌跡
//查詢軌跡,不包含散點軌跡
fun queryTrack(trackId:Long) {
aMapTrackClient.queryTerminal(QueryTerminalRequest(AccountConfig.SERVICE_ID, AccountConfig.TERMINAL_NAME),
object : SimpleOnTrackListener() {
override fun onQueryTerminalCallback(queryTerminalResponse: QueryTerminalResponse?) {
if (queryTerminalResponse?.isSuccess!!) {
if (queryTerminalResponse.isTerminalExist) {
val tid = queryTerminalResponse.tid
//設(shè)置軌跡查詢的條件
val queryTrackRequest = QueryTrackRequest(
AccountConfig.SERVICE_ID,//軌跡服務(wù)id
tid, //終端id
trackId, // 軌跡id暖途,不指定的話傳入-1卑惜,查詢所有軌跡,注意分頁僅在查詢特定軌跡id時生效驻售,查詢所有軌跡時無法對軌跡點進行分頁
TimeUtils.getUnixTimeFromDate(track.startTime, TimeUtils.FORMAT_YMD_HMS),//開始時間
TimeUtils.getUnixTimeFromDate(track.endTime, TimeUtils.FORMAT_YMD_HMS),//結(jié)束時間
1, // 啟用去噪
if (isBindRoad) 1 else 0,// 是否綁路
0, // 不進行精度過濾
DriveMode.DRIVING, // 當(dāng)前僅支持駕車模式
if (isRecoup) 1 else 0, // 距離補償
1000, // 距離補償露久,只有超過1km的點才啟用距離補償
1, // 結(jié)果應(yīng)該包含軌跡點信息
1, // 返回第1頁數(shù)據(jù),如果未指定軌跡欺栗,分頁將失效
100 // 一頁不超過100條
)
aMapTrackClient.queryTerminalTrack(queryTrackRequest,
object : SimpleOnTrackListener() {
override fun onQueryTrackCallback(queryTrackResponse: QueryTrackResponse?) {
if (queryTrackResponse?.isSuccess!!) {
//如果指定了軌跡id毫痕,返回一條,如果不指定的話迟几,返回指定時間段內(nèi)的所有軌跡
val tracks = queryTrackResponse.tracks
if (tracks.isNotEmpty()) {
var allEmpty = true
for (track in tracks) {
val points = track.points //軌跡點集合
if (points != null && points.size > 0) {
allEmpty = false
//將軌跡繪制到地圖上
drawTrackOnMap(points, textureMapView)
}
}
if (allEmpty) run {
//所有軌跡都無軌跡點消请,請嘗試放寬過濾限制,如:關(guān)閉綁路模式
} else {
val sb = StringBuilder()
sb.append("共查詢到").append(tracks.size).append("條軌跡类腮,每條軌跡行駛距離分別為:")
for (track in tracks) {
sb.append(track.distance).append("m,")
}
sb.deleteCharAt(sb.length - 1)
}
} else {
//未獲取到軌跡
}
} else {
//查詢歷史軌跡失敗
}
}
})
} else {
//Terminal不存在
}
} else {
//"網(wǎng)絡(luò)請求失敗臊泰,錯誤原因: " + queryTerminalResponse.errorMsg
}
}
})
}
注意:查詢軌跡設(shè)置的時間段不能超過24小時,如果超過24小時蚜枢,直接查詢失敗缸逃。
QueryTrackRequest API文檔
QueryTerminalResponse API文檔
3.繪制軌跡
通過軌跡id查詢獲取到該軌跡對應(yīng)的所有軌跡點,可以將這些軌跡點繪制到地圖上厂抽,行成一條軌跡察滑,為了顯示的軌跡更加的平滑,需要對這些軌跡點進行平滑處理(對真實軌跡進行處理修肠,實現(xiàn)去噪贺辰、平滑和抽稀):官方示例嵌施。
/**
* 在地圖上繪制軌跡繪制到地圖上
* @param points List<Point>:軌跡點集合
* @param textureMapView:地圖View
*/
private fun drawTrackOnMap(points: List<Point>, textureMapView: TextureMapView) {
//軌跡平滑處理
val pathoptimizeList = trackSmoothHandle(points)
val boundsBuilder = LatLngBounds.Builder()
val polylineOptions = PolylineOptions()
//設(shè)置軌跡的顏色
polylineOptions.color(mContext.resources.getColor(R.color.baseColorBlue)).width(20f)
var endIndex = pathoptimizeList.size - 1
if (pathoptimizeList.isNotEmpty()) {
// 起點
val latLng = pathoptimizeList[0]
val p = Point()
val markerOptions = MarkerOptions()
.position(latLng)
.title("起點") //彈窗標(biāo)題
.infoWindowEnable(true) //顯示彈窗
.snippet("點擊起點圖標(biāo)彈窗顯示的內(nèi)容")
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_GREEN)) //起點圖標(biāo)
endMarkers.add(textureMapView.map.addMarker(markerOptions))
}
if (pathoptimizeList.size > 1) {
// 終點
val latLng = pathoptimizeList[endIndex]
val p = Point()
p.lat = latLng.latitude
p.lng = latLng.longitude
val markerOptions = MarkerOptions()
.position(latLng)
.title("終點")
.infoWindowEnable(true)
.snippet(snippet)
//.icon(BitmapDescriptorFactory.fromBitmap(MarkerView(mContext).getBitmap())) 自定義圖標(biāo)
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_BLUE))
endMarkers.add(textureMapView.map.addMarker(markerOptions))
}
for (latLng in pathoptimizeList.subList(0, endIndex + 1)) {
polylineOptions.add(latLng)
boundsBuilder.include(latLng)
}
val polyline = textureMapView.map.addPolyline(polylineOptions)
polylines.add(polyline)
val height = ScreenUtil.getScreenHeight(mContext)
val width = ScreenUtil.getScreenWidth(mContext)
//設(shè)置軌跡顯示的區(qū)域 animateCamera方法:以動畫方式按照傳入的CameraUpdate參數(shù)更新地圖狀態(tài)饲化,默認(rèn)動畫耗時250毫秒。
textureMapView.map.animateCamera(
CameraUpdateFactory.newLatLngBoundsRect(
boundsBuilder.build(),
ScreenUtil.dip2px(40f, mContext),
ScreenUtil.dip2px(40f, mContext),
height / 8,
height / 5 * 3
)
)
}
/**
* 軌跡平滑處理
* @param points List<Point>:原軌跡點集合
* @return List<LatLng>:平滑處理后的軌跡點集合
*/
fun trackSmoothHandle(points: List<Point>):List<LatLng>{
val mpathSmoothTool = PathSmoothTool()
//設(shè)置平滑處理的等級
mpathSmoothTool.intensity = 4
val mOriginList = ArrayList<LatLng>()
points.forEach {
mOriginList.add(LatLng(it.lat, it.lng))
}
return mpathSmoothTool.pathOptimize(mOriginList)
}
MarkerOptions API文檔
軌跡點Point包含的
設(shè)置軌跡顯示的區(qū)域API文檔