基于高德地图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。
程序员这个职业就是缺少运动,出去跑跑步还是挺好的,最后祝大家身体健康,哈哈。
以上。