基于高德地图SDK实现跑步路线踩点

前言

  现在市面上有很多跑步的app,主要的功能大致是记录你的跑步路线以及你的跑步状况,速度距离等,大多是使用了高德地图,百度地图之类的SDK。
  之前学校里有一款课外体育考勤的跑步APP,叫运动世界校园,应该很多大学生都有在用吧(被迫的……),为了那几个少得可怜的体育分,一个学期要跑满120公里,很招人烦了,不过现在已经大四了,不用再跑了嘿嘿。扯开了。
  回到正题,这篇文章主要介绍了如何实现类似运动世界校园跑步的跑步踩点功能。

怎么实现

  现在市面上就那么几家地图的巨头,我这边选择了高德地图来开发,主要是因为公司里开发用的也是高德地图,有过相关的使用经验。高德开放平台 上有很多地图相关的功能SDK,可以了解一下。
  首先我们要集成高德SDK,官网上有详细的配置教程 Android地图SDK集成 ,我这里不详细介绍了,按照教程里一步一步来,就可以完成最简单的地图显示。集成好后,差不多长这样。

  这仅仅是个地图展示的功能。如果要实现跑步路线的展示,我们需要获得当前定位的经纬度信息。高德SDK提供了一个监听接口 AMap.OnMyLocationChangeListener,实现这个接口我们就可以获得定位的经纬度信息,我们让承载地图的 Activity 实现这个接口,重写其 onMyLocationChange(Location location) 方法,方法中的参数 location 便是我们当前位置的信息。

public class MainActivity extends AppCompatActivity implements AMap.OnMyLocationChangeListener {

@Override
    public void onMyLocationChange(Location location) {
      // location
    }
}

  拿到位置的信息之后,我们就可以进行路线的绘制了,因为这个位置信息是固定时间间隔获取(差不多每两秒获取一次),所以说,绘制的思路是,拿到当前位置的经纬度和前一次定位的经纬度,将这两个经纬度点连起来,就是我们的运动路径,每两秒画一段路径,连起来就是我们跑步的路线了。高德地图提供了在地图上绘制线的方法。

        //绘制移动路线
        mAMap.addPolyline(new PolylineOptions().add(lastLatLng, currentLatLng).width(10).color(Color.argb(255, 1, 1, 1)));

  只要传入两个点经纬度的信息,就可以在这两个点之间画一条线。这样,我们就实现了跑步路线绘制的功能。
  关于踩点的功能,要踩点,我们首先要在地图上标出我们的点。高德地图也提供了绘制标记点的相关方法。

MarkerOptions markerOption = new MarkerOptions();
markerOption.position(latLng)
            .icon(BitmapDescriptorFactory.fromBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.not_arrived)));

  只要传入标记点的经纬度信息,调用此方法便可以将标记点绘制在地图上,我们要做的就是将那些标记点的经纬度信息准备好。我这里准备了一些标记点的经纬度。

static ArrayList<LatLng> getLatLngs(){
        ArrayList<LatLng> latLngs = new ArrayList<>();
        latLngs.add(new LatLng(30.3126719672,120.3566998243));
        latLngs.add(new LatLng(30.3121857093,120.3566837311));
        latLngs.add(new LatLng(30.3116485074,120.3563940525));
        latLngs.add(new LatLng(30.3119078466,120.3556001186));
        latLngs.add(new LatLng(30.3128757317,120.3556591272));
        latLngs.add(new LatLng(30.3133573552,120.3570002317));
        return latLngs;
    }

  这些点在地图上绘制出来的效果是这样的。



  标记点绘制好了,接下来的问题就是如何去判定踩点这个事件,判定的方法还是利用之前获取的位置信息。所谓踩点,就是你人的位置,在标记点所在的位置,两者的经纬度相同,人就踩到了这个点上。但是由于GPS的定位并非百分百的精确,所以踩点事件的触发,我们将其定义为,人的位置相对于标记点的位置的直线距离小于某个值,便踩到了点。
  高德地图很贴心的为我们提供了计算两个经纬度之间距离的方法。

AMapUtils.calculateLineDistance(currentLatLng, marker.getPosition())

  只要传入两个点的经纬度值,就可以得到两点之间的直线距离,我们拿这个距离作踩点的判断。每次获得到当前坐标的经纬度,就与各个标记点计算距离,当距离小于判定距离,就记已经踩到这个点。

        //计算当前定位与各个标记点的距离,如果小于判定距离,则认为到达此标记点
        Observable.fromIterable(markers)
                .filter(new Predicate<Marker>() {
                    @Override
                    public boolean test(Marker marker) throws Exception {
                        return AMapUtils.calculateLineDistance(currentLatLng, marker.getPosition()) < DISTANCE_TO_MARKER;
                    }
                })
                .subscribe(new Consumer<Marker>() {
                    @Override
                    public void accept(Marker marker) throws Exception {
                        //到达标记点后,将标记点设为已到达的样式
                        marker.setIcon(BitmapDescriptorFactory.fromBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.arrived)));
                    }
                });

  距离的判断我用到了Rxjava的过滤器,每次获取到经纬度过后在标记点列表中过滤出距离小于判定距离的标记点,将它设置为已踩点。



  这里对标记点颜色做了处理,当踩到这个标记点后,标记点的颜色就变成黄色。至此,跑步路线踩点功能就实现了,是不是很简单。
  我这里贴一下核心功能的代码,相关方法已经写上注释。

/**
 * @author chaochaowu
 */
public class MainActivity extends AppCompatActivity implements AMap.OnMyLocationChangeListener {

    @BindView(R.id.tv_distance)
    TextView mTvDistance;
    @BindView(R.id.tv_speed)
    TextView mTvSpeed;
    @BindView(R.id.map)
    MapView mMapView;

    private Context mContext;
    /**
     * 当前定位与标记点的判断距离,小于这个距离说明已到达标记点
     */
    private static final int DISTANCE_TO_MARKER = 20;
    /**
     * 异常距离,如果超过这个距离,则说明移动距离异常,避免定位抖动造成的误差
     */
    private static final int DISTANCE_ERROR = 50;
    private AMap mAMap;
    private ArrayList<Marker> markers;
    private LatLng currentLatLng;
    private long totalDistance;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getSupportActionBar().hide();
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        mMapView.onCreate(savedInstanceState);
        mContext = this;
        //初始化地图
        initMap();
        //在地图上绘制标记点
        drawMarkers(Utils.getLatLngs());
        //设置位置变化的监听
        mAMap.setOnMyLocationChangeListener(this);
    }

    /**
     * 初始化地图参数
     */
    private void initMap() {
        mAMap = mMapView.getMap();
        MyLocationStyle myLocationStyle;
        myLocationStyle = new MyLocationStyle();
        myLocationStyle.showMyLocation(true);
        myLocationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE);
        myLocationStyle.interval(2000);
        mAMap.setMyLocationStyle(myLocationStyle);
        mAMap.moveCamera(CameraUpdateFactory.zoomTo(19));
        mAMap.setMyLocationEnabled(true);
        mAMap.getUiSettings().setMyLocationButtonEnabled(true);
        mAMap.getUiSettings().setCompassEnabled(true);
        mAMap.getUiSettings().setScaleControlsEnabled(true);
    }

    /**
     * 在地图上绘制标记点
     *
     * @param latLngs 标记点的经纬度
     */
    private void drawMarkers(ArrayList<LatLng> latLngs) {
        markers = new ArrayList<>();
        for (LatLng latLng : latLngs) {
            MarkerOptions markerOption = new MarkerOptions();
            markerOption.position(latLng)
                    .icon(BitmapDescriptorFactory.fromBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.not_arrived)));
            markers.add(mAMap.addMarker(markerOption));
        }
    }

    /**
     * 位置变化的监听操作
     *
     * @param location 位置变化后的位置
     */
    @Override
    public void onMyLocationChange(Location location) {
        //首次定位时设置当前经纬度
        if (currentLatLng == null) {
            currentLatLng = new LatLng(location.getLatitude(), location.getLongitude());
        }
        LatLng lastLatLng = currentLatLng;
        currentLatLng = new LatLng(location.getLatitude(), location.getLongitude());
        //计算当前定位与前一次定位的距离,如果距离异常或是距离为0,则不做任何操作
        float movedDistance = AMapUtils.calculateLineDistance(currentLatLng, lastLatLng);
        if (movedDistance > DISTANCE_ERROR || movedDistance == 0) {
            return;
        }
        //绘制移动路线
        mAMap.addPolyline(new PolylineOptions().add(lastLatLng, currentLatLng).width(10).color(Color.argb(255, 1, 1, 1)));
        totalDistance += movedDistance;
        //在界面上显示总里程和当前的速度
        displayInfo(totalDistance,location.getSpeed());
        //计算当前定位与各个标记点的距离,如果小于判定距离,则认为到达此标记点
        Observable.fromIterable(markers)
                .filter(new Predicate<Marker>() {
                    @Override
                    public boolean test(Marker marker) throws Exception {
                        return AMapUtils.calculateLineDistance(currentLatLng, marker.getPosition()) < DISTANCE_TO_MARKER;
                    }
                })
                .subscribe(new Consumer<Marker>() {
                    @Override
                    public void accept(Marker marker) throws Exception {
                        //到达标记点后,将标记点设为已到达的样式
                        marker.setIcon(BitmapDescriptorFactory.fromBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.arrived)));
                    }
                });
    }

    /**
     * 在界面上显示总路程和当前的速度
     * @param totalDistance 总路程
     * @param speed 当前速度
     */
    @SuppressLint("DefaultLocale")
    private void displayInfo(long totalDistance, float speed) {
        mTvDistance.setText(String.format("总路程:%d m", totalDistance));
        mTvSpeed.setText(String.format("当前速度: %s m/s", speed));
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mMapView.onDestroy();
    }

    @Override
    protected void onResume() {
        super.onResume();
        mMapView.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        mMapView.onPause();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        mMapView.onSaveInstanceState(outState);
    }

}

最后

  功能的实现并不复杂,主要的工作就是集成SDK 和 onMyLocationChange(Location location) 这个位置获取方法的处理,最大的坑其实是在集成 SDK 和地图的相关配置上,只要将高德地图配置好,就可以为所欲为了。这里也贴上这个项目的 GitHub 地址 基于高德地图的跑步路线踩点 。如果需要编译运行的话,需要设置你的开发环境下的开发者key。
  程序员这个职业就是缺少运动,出去跑跑步还是挺好的,最后祝大家身体健康,哈哈。

以上。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,864评论 6 494
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,175评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,401评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,170评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,276评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,364评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,401评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,179评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,604评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,902评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,070评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,751评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,380评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,077评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,312评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,924评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,957评论 2 351

推荐阅读更多精彩内容

  • 跑步运动软件最基本的功能之一,就是对运动中的用户进行实时定位,并绘制出运动路径。本文主要内容,就是用高德的SDK实...
    msq3阅读 7,694评论 10 24
  • 该文章属于刘小壮原创,转载请注明:刘小壮[https://www.jianshu.com/u/2de707c93d...
    刘小壮阅读 26,886评论 63 143
  • 文言识知题词 语难尽安释怀 粉泽韵丽芳容 煮粉哗娜四载 扯突等郡组画 留照阶魏斐赵 扎钟时晓扰攘 愁入素芸众生 青...
    修农阅读 324评论 4 5
  • 晚,雨后的空气格外清新,走在老家的巷道上,空气中氤氲着清幽的香气,我猜,是哪家的栀子花开了!我不喜欢太浓烈...
    梦千年76阅读 480评论 0 4