先上一张效果图
一.在高德控制台创建应用获取key
这一步比较简单,没什么可说的。如何申请 Key
二.接入工程
1.通过Android Studio引入相关的包,官方文档说的比较明确,这里不再阐述,附上相关的链接:Android Studio 配置工程。
2.配置清单文件,官网文档这里没有细说,具体可以查看示例代码中的配置,这里直接贴出相关配置。
<?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"/>
<!--用于获取运营商信息,用于支持提供运营商信息相关的接口-->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<!--用于访问wifi网络信息,wifi信息会用于进行网络定位-->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<!--用于获取wifi的获取权限,wifi信息会用来进行网络定位-->
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<!--用于访问网络,网络定位需要上网-->
<uses-permission android:name="android.permission.INTERNET"/>
<!--用于读取手机当前的状态-->
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<!--用于写入缓存数据到扩展存储卡-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!--用于申请调用A-GPS模块-->
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
<!--用于申请获取蓝牙信息进行室内定位-->
<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的动态权限申请。
三.获取Service ID
- Service
即猎鹰轨迹服务。一个service对应一个轨迹管理系统,通过一个service可管理多个终端设备(即terminal),service的唯一标识符是sid。举例说明,针对某网约车公司的轨迹管理系统,可以创建一个service,在创建的service中管理所有营运车辆的轨迹。
- Terminal
一个terminal代表现实中一个终端设备,它可以是个体、也可以是一辆车或者任何运动的物体。在同一个service中,terminal以
tid
作为唯一标识。
要使用轨迹服务,必须先创建一个service,通过请求相应的接口来获取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("服务id:$json")
val data = Gson().fromJson(json, TrackServiceEntity::class.java)
AccountConfig.SERVICE_ID = data.data.sid.toLong()
}
})
}
有关接口的参数和返回值的详细说明见:接口说明。
这里重点说一下参数key这个参数值。它是用户在高德地图官网申请Web服务API类型KEY,官网文档在这说的不是很详细。我们怎么获取这个key呢?在我们上面创建的应用下,去添加一个web类型的key:
输入自定义的名字后提交即可获取web类型的key:
这一步获取的key就是就是上面请求中key对应的值。
注意:service id 只需要获取一次,不需要每次使用的时候都去请求获取,除非添加新的service。(每个 Key 下最多注册15个 Service)
通过以上步骤,轨迹相关的配置已经完成,下面就具体看一下代码的实现。
四.相关代码
使用轨迹服务,首先需要初始化猎鹰sdk服务类以及一些参数的配置:
fun init(context: Context) {
aMapTrackClient = AMapTrackClient(context)
//将定位信息采集周期设置为2s,上报周期设置为20s,注意定位信息采集周期的范围应该是1s~60s,上报周期的范围是采集周期的5~50倍。
aMapTrackClient.setInterval(2, 20)
//设置轨迹点缓存大小为20M,最大为50M,默认值为50
aMapTrackClient.setCacheSize(20)
//设置定位模式,默认为高精度
aMapTrackClient.setLocationMode(LocationMode.HIGHT_ACCURACY)
}
定位模式:
- HIGHT_ACCURACY(高精度定位模式):
在这种定位模式下,将同时使用高德网络定位和卫星定位,优先返回精度高的定位
- BATTERY_SAVING(低功耗定位模式):
在这种模式下,将只使用高德网络定位。使用网络定位就是使用基站定位。
- DEVICE_SENSORS(仅设备定位模式):
在这种模式下,将只使用卫星定位。
其次需要创建用于接收猎鹰sdk服务启停状态的监听器:
//SimpleOnTrackLifecycleListener是实现接口OnTrackLifecycleListener的一个类,我们继承它只需要重写需要的方法即可。
onTrackLifecycleListener = object : SimpleOnTrackLifecycleListener() {
override fun onBindServiceCallback(status: Int, msg: String?) {
Logger.e("onBindServiceCallback, status: $status, msg: $msg")
}
//轨迹上报服务启动回调
override fun onStartTrackCallback(status: Int, msg: String?) {
if (status == ErrorCode.TrackListen.START_TRACK_SUCEE || status == ErrorCode.TrackListen.START_TRACK_SUCEE_NO_NETWORK) {
//启动动轨迹上报服务成功,开始轨迹采集
startGather()
isServiceRunning = true
} else if (status == ErrorCode.TrackListen.START_TRACK_ALREADY_STARTED) {
//轨迹上报服务已经启动,重复启动
isServiceRunning = true
} else {
//"error onStartTrackCallback, status: $status, msg: $msg"
}
}
//轨迹上报服务停止回调
override fun onStopTrackCallback(status: Int, msg: String?) {
if (status == ErrorCode.TrackListen.STOP_TRACK_SUCCE) {
//停止轨迹上报服务成功
isServiceRunning = false
isGatherRunning = false
} else {
//"error onStopTrackCallback, status: $status, msg: $msg"
}
}
//定位采集开启回调
override fun onStartGatherCallback(status: Int, msg: String?) {
when (status) {
ErrorCode.TrackListen.START_GATHER_SUCEE -> {
//定位采集开启成功
isGatherRunning = true
}
ErrorCode.TrackListen.START_GATHER_ALREADY_STARTED -> {
//定位采集已经开启,重复启动
isGatherRunning = true
}
else -> {
//"error onStartGatherCallback, status: $status, msg: $msg"
}
}
}
//停止定位采集回调
override fun onStopGatherCallback(status: Int, msg: String?) {
if (status == ErrorCode.TrackListen.STOP_GATHER_SUCCE) {
//定位采集停止成功,停止轨迹上报服务
stopTrack()
isGatherRunning = false
} else {
//"error onStopGatherCallback, status: $status, msg: $msg"
}
}
}
注意:要开启定位采集,需要首先启动轨迹上报服务,等服务启动成功后才能开启定位采集。
OnTrackLifecycleListener API文档
1.轨迹采集
轨迹采集需要提供服务id及终端id,服务id在上面通过请求相关接口已经获取到;终端id需要在该终端第一次使用轨迹采集的时候创建,所以,在使用轨迹采集的时候,需要先查询该终端id,如果没有创建过就创建一个新的,如果创建过就使用查询得到的终端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) {
// 当前终端已经创建过,直接使用查询到的terminal id
collectionTrack(queryTerminalResponse.tid)
} else {
// 当前终端是新终端,还未创建过,创建该终端并使用新生成的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 {
//"网络请求失败," + queryTerminalResponse.errorMsg
}
}
})
}
获取对应的终端id后,开始轨迹收集:
private fun collectionTrack(terminalId: Long) {
this.terminalId = terminalId
if (uploadToTrack) {
newTrack()
} else {
appendTrack()
}
}
//不创建新的轨迹
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)
}
//创建一个新的轨迹
private fun newTrack() {
aMapTrackClient.addTrack(AddTrackRequest(AccountConfig.SERVICE_ID, terminalId),
object : SimpleOnTrackListener() {
override fun onAddTrackCallback(addTrackResponse: AddTrackResponse?) {
if (addTrackResponse?.isSuccess!!) {
// trackId需要在启动服务后设置才能生效,因此这里不设置,而是在startGather之前设置了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 {
//"网络请求失败," + addTrackResponse.errorMsg
}
}
})
}
如果创建一个新的轨迹,那么每一个行程对应着一条轨迹。
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
//设置轨迹查询的条件
val queryTrackRequest = QueryTrackRequest(
AccountConfig.SERVICE_ID,//轨迹服务id
tid, //终端id
trackId, // 轨迹id,不指定的话传入-1,查询所有轨迹,注意分页仅在查询特定轨迹id时生效,查询所有轨迹时无法对轨迹点进行分页
TimeUtils.getUnixTimeFromDate(track.startTime, TimeUtils.FORMAT_YMD_HMS),//开始时间
TimeUtils.getUnixTimeFromDate(track.endTime, TimeUtils.FORMAT_YMD_HMS),//结束时间
1, // 启用去噪
if (isBindRoad) 1 else 0,// 是否绑路
0, // 不进行精度过滤
DriveMode.DRIVING, // 当前仅支持驾车模式
if (isRecoup) 1 else 0, // 距离补偿
1000, // 距离补偿,只有超过1km的点才启用距离补偿
1, // 结果应该包含轨迹点信息
1, // 返回第1页数据,如果未指定轨迹,分页将失效
100 // 一页不超过100条
)
aMapTrackClient.queryTerminalTrack(queryTrackRequest,
object : SimpleOnTrackListener() {
override fun onQueryTrackCallback(queryTrackResponse: QueryTrackResponse?) {
if (queryTrackResponse?.isSuccess!!) {
//如果指定了轨迹id,返回一条,如果不指定的话,返回指定时间段内的所有轨迹
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 {
//所有轨迹都无轨迹点,请尝试放宽过滤限制,如:关闭绑路模式
} 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 {
//"网络请求失败,错误原因: " + queryTerminalResponse.errorMsg
}
}
})
}
注意:查询轨迹设置的时间段不能超过24小时,如果超过24小时,直接查询失败。
QueryTrackRequest API文档
QueryTerminalResponse API文档
3.绘制轨迹
通过轨迹id查询获取到该轨迹对应的所有轨迹点,可以将这些轨迹点绘制到地图上,行成一条轨迹,为了显示的轨迹更加的平滑,需要对这些轨迹点进行平滑处理(对真实轨迹进行处理,实现去噪、平滑和抽稀):官方示例。
/**
* 在地图上绘制轨迹绘制到地图上
* @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()
//设置轨迹的颜色
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("起点") //弹窗标题
.infoWindowEnable(true) //显示弹窗
.snippet("点击起点图标弹窗显示的内容")
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_GREEN)) //起点图标
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())) 自定义图标
.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)
//设置轨迹显示的区域 animateCamera方法:以动画方式按照传入的CameraUpdate参数更新地图状态,默认动画耗时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()
//设置平滑处理的等级
mpathSmoothTool.intensity = 4
val mOriginList = ArrayList<LatLng>()
points.forEach {
mOriginList.add(LatLng(it.lat, it.lng))
}
return mpathSmoothTool.pathOptimize(mOriginList)
}
MarkerOptions API文档
轨迹点Point包含的
设置轨迹显示的区域API文档