实现百度地图marker类平滑移动

先讲一下思路,再贴代码

效果

车辆的图片比较难找,最后随便找张图片


背景

公司说要实现一个车辆监控功能,所以先给了我一些经纬度,叫我模拟车辆移动的过程.
刚开始实现的方式就是简单的将车辆不断的设置到下一个经纬度,但这种实现方式有一个问题,就是将地图拉大的时候车辆看起来明显不是在移动,是在跳到某个点.
然后看到web端的效果是真正的在移动,但不断地查百度的api都没有可以让marker移动的方法.

实现思路

最后想出一种方式,就是将2个点分解了n多个点,让车辆更加频繁的跳,从而实现类平滑移动.然后看到百度地图最大可以放大到5m/cm,

所以决定设置最大方法20m/cm,然后2点之间每个5米移动一次,也就是如果将地图缩放到最大,车辆每次移动的距离是0.25cm,这个时候肉眼看起来就比较像在移动

最后经过测试,当手机有点卡的时候,看起来还是有点假.当手机比较流畅的时候,看起来就真的是在移动.当然,这个睡眠时间和移动距离还是要根据实际情况进行调整

关于画线

如果按照最简单的方式,每2个点画一条线,你会发现走不到一半整个地图就卡得要死.

原因是画的线太多了,本身就已经给了200多个点,然后还要将这200多个点每2个点分成n多个点,这个时候至少都要有1,2千个点吧,所以要转换一下实现思路.

最后想到的办法是:当车辆在2个点中的点行驶的时候,不断的画线.当行驶完成后,再从第一个点和中间经过的点画一条线,再把这些小线清除掉.这样地图上就只保留一条线,也就不卡了.

可能表达得有问题,有人看不懂.举例:第一个点和第二个点之间相距20米,这个时候车辆在走完这段距离的时候会画4条线,当走玩之后就在第一个点和第二个点上画一条线,再清除这4条小线.

第二个点到第三个点:同理,中间画4条小线.最后,在百度地图上完整的线就是,第一个点到第二个点,第二个点到第三个点的线,再清除小线.这样就看起来很像边走边画

实现代码

经纬度请自行提供

class CarMoveActivity : BaseActivity(), View.OnClickListener {
    private val TAG = "CarMoveActivityMsg"
    private lateinit var mBaiduMap: BaiduMap
    private lateinit var locationClient: LocationClient
    private val MOVE_DISTANCE = 5
    private val SLEEP_TIME = 20L
    private lateinit var mLastMarker: Marker
    private lateinit var mLastLatLng: LatLng
    private var mCurrentPoint: LatLng? = null
    private var mLastLine: Polyline? = null
    private lateinit var mCarIcon: BitmapDescriptor
    private var isStop = false
    private var isPause = false
    private var isRunning = false
    private var mFirstIndex = 0
    private var mSecondIndex = 0
    private val MAX_LEVEL = 19F
    private val MIN_LEVEL = 3F
    override fun setContentView() {
        SDKInitializer.initialize(application)
        setContentView(R.layout.activity_car_move)
    }

    override fun initView() {
        mBaiduMap = car_move_map.map
        mBaiduMap.setMaxAndMinZoomLevel(MAX_LEVEL, MIN_LEVEL)
        ViewUtil.setOnClick(this, car_move_start, car_move_stop, car_move_pause, cae_move_tocar)
    }

    override fun initData() {
        setLocationOption()
        mCarIcon = BitmapDescriptorFactory.fromResource(R.mipmap.car_icon3)
    }

    override fun onClick(v: View) {
        when (v.id) {
            R.id.car_move_start -> {
                carMove()
            }
            R.id.car_move_stop -> {
                //之所以使用判断而不使用,async的cancel方法
                //是因为使用boolean可以控制代码执行流程,使用cancel有可能取消的时候某写关键代码只执行了
                isStop = true
                isRunning = false
                async {
                    //有可能会重新赋值,所以先睡一会再赋值
                    Thread.sleep(100)
                    mFirstIndex = 0
                    mSecondIndex = 0
                }
            }
            R.id.car_move_pause -> {
                isStop = true
                isPause = true
                isRunning = false
            }
            R.id.cae_move_tocar -> {
                if (mCurrentPoint != null) {
                    val u = MapStatusUpdateFactory.newLatLng(mCurrentPoint)
                    mBaiduMap.animateMapStatus(u)
                }
            }
        }
    }

    private fun setLocationOption() {
        locationClient = LocationClient(this) // 实例化LocationClient类
        locationClient.registerLocationListener(bdLocationListener) // 注册监听函数

        val option = LocationClientOption()
        option.locationMode = LocationClientOption.LocationMode.Hight_Accuracy//可选,默认高精度,设置定位模式,高精度,低功耗,仅设备
        option.setCoorType("bd09ll")//可选,默认gcj02,设置返回的定位结果坐标系
        option.setScanSpan(0)//可选,默认0,即仅定位一次,设置发起定位请求的间隔需要大于等于1000ms才是有效的
        option.setIsNeedAddress(true)//可选,设置是否需要地址信息,默认不需要
        option.isOpenGps = true//可选,默认false,设置是否使用gps
        option.isLocationNotify = true//可选,默认false,设置是否当gps有效时按照1S1次频率输出GPS结果
        option.setIsNeedLocationDescribe(true)//可选,默认false,设置是否需要位置语义化结果,可以在BDLocation.getLocationDescribe里得到,结果类似于“在北京天安门附近”
        option.setIsNeedLocationPoiList(true)//可选,默认false,设置是否需要POI结果,可以在BDLocation.getPoiList里得到
        option.setIgnoreKillProcess(false)//可选,默认true,定位SDK内部是一个SERVICE,并放到了独立进程,设置是否在stop的时候杀死这个进程,默认不杀死
        option.SetIgnoreCacheException(false)//可选,默认false,设置是否收集CRASH信息,默认收集
        option.setEnableSimulateGps(false)//可选,默认false,设置是否需要过滤gps仿真结果,默认需要
        locationClient.locOption = option

        locationClient.start()
    }

    private val bdLocationListener = BDLocationListener {
        with(it) {
            JLog.v(TAG, "mCurrentLatLng,lat:$latitude,lng:$longitude")
            val u = MapStatusUpdateFactory.newLatLng(LatLng(latitude, longitude))
            mBaiduMap.animateMapStatus(u)
            locationClient.stop()
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        mCarIcon.recycle()
    }

    private var list1 = LatLngUtil.getList()
    private var list = ArrayList<ArrayList<LatLng>>()
    private fun carMove() = async {
        if (isRunning) {
            return@async
        }
        isRunning = true
        isStop = true
        if (mFirstIndex == 0 && mSecondIndex == 0) {
            list1 = subLatLng4Distance(list1, 20)
            list = splitLatLng4MoveDistance(list1)
            mBaiduMap.clear()
            val ood = MarkerOptions().anchor(0.5f, 0.5f).icon(mCarIcon).position(list1[0]).rotate(rotation(list1[0], list1[1]))
            mLastMarker = mBaiduMap.addOverlay(ood) as Marker
            mLastLatLng = list1[0]
        }
        isStop = false
        //由于list和list1的长度一样,所以用哪个size都一样
        //为什么要+1,看下面的代码再理解一下就知道了,解释起来有点麻烦,反正就是为了暂停功能
        for (i in mFirstIndex + 1 until list1.size) {
            if (isStop) {
                return@async
            }
            //下面还有一个sleep,当暂时后继续的时候,会重新执行这里,如果这里也sleep的画就会sleep2次
            if (isPause) {
                isPause = false
            } else {
                Thread.sleep(SLEEP_TIME)
            }
            moveCar(list[i], i)
            //也可以不用另外写一个方法,这样这里就不用重复判断,导致for降低效率
            if (isStop) {
                return@async
            }
            //上面的until也可以设置为size-1,这样就不用在这里做判断
            //可以去for外面判断,但一开始没多想这样写,现在也懒得改了
            if (i != list1.size - 1) {
                mLastMarker.rotate = rotation(list1[i], list1[i + 1])
            }
            mFirstIndex = i
            mSecondIndex = 0
        }
        //能执行到这里就代表车辆已经行驶完成,所以让这2个index变成0
        mFirstIndex = 0
        mSecondIndex = 0
        isRunning = false
    }

    private val LINE_COLOR = 0x5500ff00.toInt()
    private val Z_INDEX = -100
    private var mLastSmallLine: Polyline? = null
    private var mLastIndex = -1
    private fun moveCar(list: ArrayList<LatLng>, index: Int) {
        var firstLine: Polyline? = null
        //当index不等于0的时候代表这段代码已经执行过了,不能重复执行
        if (mSecondIndex == 0) {
            mLastMarker.position = list[0]
            //将上一个位置和当前位置画一条小线
            val line = PolylineOptions().width(10).color(LINE_COLOR).points(arrayListOf(mLastLatLng, list[0])).zIndex(Z_INDEX)
            mLastLatLng = list[0]
            mSecondIndex = 0

            firstLine = mBaiduMap.addOverlay(line) as Polyline
        }
        //如果该list只有一个经纬度,就没必要继续执行下去
        if (list.size == 1) {
            val latLngList = ArrayList<LatLng>()
            (0..index).mapTo(latLngList) { list1[it] }
            //画一条开始位置到现在的位置的线
            val line = PolylineOptions().width(10).color(LINE_COLOR).points(latLngList).zIndex(Z_INDEX)
            val tempLine = mBaiduMap.addOverlay(line) as Polyline
            //清除小线和到index-1的线
            firstLine?.remove()
            mLastLine?.remove()
            mLastLine = tempLine
            return
        }
        //记录一条长线中的每跳小线
        val lineList = ArrayList<Polyline>()
        if (firstLine != null) {
            lineList.add(firstLine)
        }
        //因为执行这个方法的时候,随时都有可能按下停止/暂停按钮,所以用finally
        try {
            for (i in mSecondIndex + 1 until list.size) {
                if (isStop) {
                    return
                }
                Thread.sleep(SLEEP_TIME)
                //画每一段小线,并记录
                val line = PolylineOptions().width(10).color(LINE_COLOR).points(arrayListOf(mLastLatLng, list[i])).zIndex(Z_INDEX)
                mLastMarker.position = list[i]
                mLastLatLng = list[i]
                mSecondIndex = i
                mCurrentPoint = list[i]

                lineList.add(mBaiduMap.addOverlay(line) as Polyline)
            }
        } catch (e: Exception) {
            e.printStackTrace()
        } finally {
            mLastSmallLine?.remove()
            //当不是暂停/停止的的时候
            if (!isStop) {
                val latLngList = ArrayList<LatLng>()
                (0..index).mapTo(latLngList) { list1[it] }
                //绘制开始位置到现在位置的线
                val line = PolylineOptions().width(10).color(LINE_COLOR).points(latLngList).zIndex(Z_INDEX)
                val tempLine = mBaiduMap.addOverlay(line) as Polyline
                //清除所有小线和index-1的线
                lineList.map { it.remove() }
                mLastLine?.remove()
                mLastLine = tempLine
                //一条直线画完之后必须置为空
                mLastSmallLine = null
            } else {
                lineList.map { it.remove() }
                val size = lineList.size
                val line: PolylineOptions
                if (mLastSmallLine == null) {//当第一次进来的时候
                    //从开始位置到当前位置画一条线
                    line = PolylineOptions().width(10).color(LINE_COLOR).points(arrayListOf(lineList[0].points[0], lineList[size - 1].points[1])).zIndex(Z_INDEX)
                } else {//当第二次进来的时候
                    //从线的开始位置到当前位置
                    line = PolylineOptions().width(10).color(LINE_COLOR).points(arrayListOf(mLastSmallLine!!.points[0], lineList[size - 1].points[1])).zIndex(Z_INDEX)
                }
                mLastSmallLine = mBaiduMap.addOverlay(line) as Polyline
            }
            mLastIndex = index
        }
    }


    private infix fun LatLng.add(latLng: LatLng): LatLng {
        val ll = LatLng(latitude + latLng.latitude, longitude + latLng.longitude)
        return ll
    }

    private infix fun LatLng.sub(latLng: LatLng): LatLng {
        val ll = LatLng(latitude - latLng.latitude, longitude - latLng.longitude)
        return ll
    }

    private infix fun LatLng.mul(count: Int): LatLng {
        val ll = LatLng(latitude * count, longitude * count)
        return ll
    }

    private infix fun LatLng.div(count: Int): LatLng {
        val ll = LatLng(latitude / count, longitude / count)
        return ll
    }
    //将一堆经纬度里面一些距离过近的经纬度去除,否则一些距离过近又不同方向的经纬度就会看来像再原地打转
    private fun subLatLng4Distance(list1: ArrayList<LatLng>, distance: Int): ArrayList<LatLng> {
        if (list1.size <= 1) {
            return list1
        }
        val list = ArrayList<LatLng>()
        list.add(list1[0])
        (1 until list1.size)
                .filter { DistanceUtil.getDistance(list1[it - 1], list1[it]) >= distance }
                .mapTo(list) { list1[it] }
        return list
    }
    //将2个点分解成n多个点
    private fun splitLatLng4MoveDistance(list1: ArrayList<LatLng>): ArrayList<ArrayList<LatLng>> {
        val list = ArrayList<ArrayList<LatLng>>()
        list.add(arrayListOf(list1[0]))
        for (i in 1 until list1.size) {
            val distance = DistanceUtil.getDistance(list1[i - 1], list1[i]).toInt()
            if (distance <= MOVE_DISTANCE) {
                list.add(arrayListOf(list1[i]))
            } else {
                //当距离和要的距离取模等于0的时候必须减1,因为最后一个点必须为第二个经纬度
                val count = distance / MOVE_DISTANCE - if (distance % MOVE_DISTANCE == 0) 1 else 0
                val subLatLng = list1[i] sub list1[i - 1]
                val divLatLng = subLatLng div count
                val list2 = ArrayList<LatLng>()
                repeat(count) {
                    val ll = list1[i - 1] add (divLatLng mul (it + 1))
                    list2.add(ll)
                }
                list2.add(list1[i])
                list.add(list2)
            }
        }
        return list
    }
    //车辆旋转
    private fun rotation(start: LatLng, end: LatLng): Float =
            if (end.longitude != start.longitude) {
                val tan = (end.latitude - start.latitude) / (end.longitude - start.longitude)
                val atan = Math.atan(tan)
                var deg = atan * 360 / (2 * Math.PI)
                deg = if (end.longitude < start.longitude) {
                    -deg + 90 + 90
                } else {
                    -deg
                }
                -deg.toFloat()
                /*deg = -deg
                deg.toFloat()*/
            } else {
                val disy = end.latitude - start.latitude
                var bias = 1
                if (disy > 0) {
                    bias = -1
                }
                -bias * 90f
                /*val deg = -bias * 90
                deg.toFloat()*/
            }
}
xml布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">
    <LinearLayout
        android:layout_width="match_parent"
        android:orientation="horizontal"
        android:layout_height="wrap_content">
        <Button
            android:id="@+id/car_move_start"
            android:text="start"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
        <Button
            android:id="@+id/car_move_stop"
            android:text="stop"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
        <Button
            android:id="@+id/car_move_pause"
            android:text="pause"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
        <Button
            android:id="@+id/cae_move_tocar"
            android:text="tocar"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    </LinearLayout>
    <com.baidu.mapapi.map.MapView
        android:id="@+id/car_move_map"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</LinearLayout>
关于车辆旋转,在百度api里面查了好久都没有查到关于计算2个点的角度的方法,网上和百度开发者论坛又查不到,数学知识也忘光了,最后从一份js代码里面翻译过来

这是js代码

ViewUtil

class ViewUtil{
    companion object {
        fun setOnClick(onclickImpl : View.OnClickListener,vararg param : View) = param.forEach { it.setOnClickListener(onclickImpl) }
    }
}

BaseActivity

abstract class BaseActivity :AppCompatActivity(){
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView()
        initView()
        initData()
    }
    protected abstract fun setContentView()
    protected abstract fun initView()
    protected abstract fun initData()
}

源码

http://download.csdn.net/download/android_upl/10134865

猜你喜欢

转载自blog.csdn.net/android_upl/article/details/78647147
今日推荐