以下是素材:
最近项目中用到了根据一段线路的经纬度集合来在地图上播放该车辆的行驶轨迹的需求.下面我就讲一下我实现步骤:
效果图如下(因为制作gif图为了控制大小去掉了很多帧,不必在意这些细节,嘿嘿!!!):
1.首先在界面上展示百度地图(这不是废话么)如果不知道怎么展示请看我之前的博客.
2.初始化控件,把需要绘制在地图上的BitmapDescriptor准备好.也一个起点和终点的图标和灰色的线和绿色的线还有一辆小车.
3.调用百度地图的api把对应的图标绘制上去也就是addOverlay()这个方法.
4.然后就是循环不停的移动小车在地图上的位置了.
完成以上四步也就完成了轨迹播放的功能
初始化控件:
//初始化控件
mMapView = findViewById(R.id.bmapView);
BaiduMap mBaiduMap = mMapView.getMap();
处理数据:(这里由于每个人的数据源不同所以逻辑也不同,此处需要把自己的逻辑写进去)
//处理数据源,把assets中的文件数据转化成一个装有位置的集合
List<Data> location = JsonReadUtil.fromJsonArray2(this, "test.json");
latLngs = new ArrayList<>();
LatLngBounds.Builder tempBounds = new LatLngBounds.Builder();
for (int i = 0; i < location.size(); i++) {
LatLng latLng = new LatLng(location.get(i).baidulat, location.get(i).baidulng);
latLngs.add(latLng);
tempBounds.include(latLng);
}
注意:tempBounds.include(latLng);这个方法只是为了让地图上展示出我所有的经纬度而设定的,可根据自己的业务需要加入与否.
把需要绘制的图标设置好:
//五个需要绘制在地图上的图标
BitmapDescriptor blueArrow = BitmapDescriptorFactory
.fromResource(R.mipmap.icon_road_blue_arrow);
BitmapDescriptor grayArrow = BitmapDescriptorFactory
.fromResource(R.mipmap.icon_road_gray_arrow);
BitmapDescriptor che = BitmapDescriptorFactory
.fromResource(R.mipmap.che);
BitmapDescriptor qi = BitmapDescriptorFactory
.fromResource(R.mipmap.qi);
BitmapDescriptor zhong = BitmapDescriptorFactory
.fromResource(R.mipmap.zhong);
调用addOverlay()方法把图标绘制上去:
//控制线条的宽度样式等(画线)
PolylineOptions ooPolyline = new PolylineOptions().customTexture(blueArrow).width(20).dottedLine(true).points(latLngs);
PolylineOptions ooPolyline2 = new PolylineOptions().customTexture(grayArrow).width(20).dottedLine(true).points(latLngs);
//在地图上批量添加(把灰线绘制入百度地图中)
mBaiduMap.addOverlay(ooPolyline);
//在地图上批量添加(把蓝线绘制入百度地图中)
mPolyline2 = (Polyline) mBaiduMap.addOverlay(ooPolyline2);
//拿到集合的第一个位置
LatLng pointQi = new LatLng(location.get(0).baidulat, location.get(0).baidulng);
//画起点的图标(也就是集合的第一个位置就是起点)
OverlayOptions optionQi = new MarkerOptions().position(pointQi).icon(qi);
//拿到集合最后一个位置
LatLng pointZhong = new LatLng(location.get(location.size() - 1).baidulat, location.get(location.size() - 1).baidulng);
//画终点的图标(也就是集合的最后一个位置就是终点)
OverlayOptions optionZhong = new MarkerOptions().position(pointZhong).icon(zhong);
//拿到集合的第一个位置
LatLng pointChe = new LatLng(location.get(0).baidulat, location.get(0).baidulng);
//画车辆的图标(因为车辆是从第一个位置开始行驶的,所以车辆的初始位置是第一个)
MarkerOptions markerOptions = new MarkerOptions().position(pointChe).icon(che);
//创建OverlayOptions的集合(把起点终点和车辆绘制在地图上)
List<OverlayOptions> options = new ArrayList<>();
options.add(optionQi);
options.add(optionZhong);
mMoveMarker = (Marker) mBaiduMap.addOverlay(markerOptions);
//(绘制在地图上)
mBaiduMap.addOverlays(options);
LatLngBounds mBaiduLatLngBounds = tempBounds.build();
mBaiduMap.animateMapStatus(MapStatusUpdateFactory.newLatLngBounds(mBaiduLatLngBounds));
循环移动:
/**
* 循环进行移动逻辑
*/
public void moveLooper() {
//为了不阻塞ui线程所以开启子线程
new Thread(new Runnable() {
@Override
public void run() {
//死循环
while (true) {
for (int i = 0; i < latLngs.size() - 1; i++) {
final LatLng startPoint = latLngs.get(i);
final LatLng endPoint = latLngs.get(i + 1);
mMoveMarker
.setPosition(startPoint);
mHandler.post(new Runnable() {
@Override
public void run() {
// refresh marker's rotate
if (mMapView == null) {
return;
}
mMoveMarker.setRotate((float) getAngle(startPoint,
endPoint));
}
});
double slope = getSlope(startPoint, endPoint);
// 是不是正向的标示
boolean isReverse = (startPoint.latitude > endPoint.latitude);
double intercept = getInterception(slope, startPoint);
double xMoveDistance = isReverse ? getXMoveDistance(slope) :
-1 * getXMoveDistance(slope);
for (double j = startPoint.latitude; !((j > endPoint.latitude) ^ isReverse);
j = j - xMoveDistance) {
LatLng latLng;
if (slope == Double.MAX_VALUE) {
latLng = new LatLng(j, startPoint.longitude);
} else {
latLng = new LatLng(j, (j - intercept) / slope);
}
final LatLng finalLatLng = latLng;
mHandler.post(new Runnable() {
@Override
public void run() {
if (mMapView == null) {
return;
}
mMoveMarker.setPosition(finalLatLng);
latLngs2.add(startPoint);
latLngs2.add(finalLatLng);
mPolyline2.setPoints(latLngs2);
}
});
try {
Thread.sleep(TIME_INTERVAL);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}).start();
}
关于计算的方法:
/**
* 根据两点算取图标转的角度
*/
private double getAngle(LatLng fromPoint, LatLng toPoint) {
double slope = getSlope(fromPoint, toPoint);
if (slope == Double.MAX_VALUE) {
if (toPoint.latitude > fromPoint.latitude) {
return 0;
} else {
return 180;
}
}
float deltAngle = 0;
if ((toPoint.latitude - fromPoint.latitude) * slope < 0) {
deltAngle = 180;
}
double radio = Math.atan(slope);
return 180 * (radio / Math.PI) + deltAngle - 90;
}
/**
* 根据点和斜率算取截距
*/
private double getInterception(double slope, LatLng point) {
return point.latitude - slope * point.longitude;
}
/**
* 算斜率
*/
private double getSlope(LatLng fromPoint, LatLng toPoint) {
if (toPoint.longitude == fromPoint.longitude) {
return Double.MAX_VALUE;
}
return ((toPoint.latitude - fromPoint.latitude) / (toPoint.longitude - fromPoint.longitude));
}
贴上我全部的代码:(具体的注释已经写的很清楚了)
private MapView mMapView;
private Polyline mPolyline2;
private Handler mHandler;
private ArrayList<LatLng> latLngs;
private Marker mMoveMarker;
// 通过设置间隔时间和距离可以控制速度和图标移动的距离
private static final int TIME_INTERVAL = 80;
private static final double DISTANCE = 0.00250;
private ArrayList<LatLng> latLngs2 = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化控件
mMapView = findViewById(R.id.bmapView);
BaiduMap mBaiduMap = mMapView.getMap();
//处理数据源,把assets中的文件数据转化成一个装有位置的集合
List<Data> location = JsonReadUtil.fromJsonArray2(this, "test.json");
latLngs = new ArrayList<>();
LatLngBounds.Builder tempBounds = new LatLngBounds.Builder();
for (int i = 0; i < location.size(); i++) {
LatLng latLng = new LatLng(location.get(i).baidulat, location.get(i).baidulng);
latLngs.add(latLng);
tempBounds.include(latLng);
}
mHandler = new Handler(Looper.getMainLooper());
//五个需要绘制在地图上的图标
BitmapDescriptor blueArrow = BitmapDescriptorFactory
.fromResource(R.mipmap.icon_road_blue_arrow);
BitmapDescriptor grayArrow = BitmapDescriptorFactory
.fromResource(R.mipmap.icon_road_gray_arrow);
BitmapDescriptor che = BitmapDescriptorFactory
.fromResource(R.mipmap.che);
BitmapDescriptor qi = BitmapDescriptorFactory
.fromResource(R.mipmap.qi);
BitmapDescriptor zhong = BitmapDescriptorFactory
.fromResource(R.mipmap.zhong);
//控制线条的宽度样式等(画线)
PolylineOptions ooPolyline = new PolylineOptions().customTexture(blueArrow).width(20).dottedLine(true).points(latLngs);
PolylineOptions ooPolyline2 = new PolylineOptions().customTexture(grayArrow).width(20).dottedLine(true).points(latLngs);
//在地图上批量添加(把灰线绘制入百度地图中)
mBaiduMap.addOverlay(ooPolyline);
//在地图上批量添加(把蓝线绘制入百度地图中)
mPolyline2 = (Polyline) mBaiduMap.addOverlay(ooPolyline2);
//拿到集合的第一个位置
LatLng pointQi = new LatLng(location.get(0).baidulat, location.get(0).baidulng);
//画起点的图标(也就是集合的第一个位置就是起点)
OverlayOptions optionQi = new MarkerOptions().position(pointQi).icon(qi);
//拿到集合最后一个位置
LatLng pointZhong = new LatLng(location.get(location.size() - 1).baidulat, location.get(location.size() - 1).baidulng);
//画终点的图标(也就是集合的最后一个位置就是终点)
OverlayOptions optionZhong = new MarkerOptions().position(pointZhong).icon(zhong);
//拿到集合的第一个位置
LatLng pointChe = new LatLng(location.get(0).baidulat, location.get(0).baidulng);
//画车辆的图标(因为车辆是从第一个位置开始行驶的,所以车辆的初始位置是第一个)
MarkerOptions markerOptions = new MarkerOptions().position(pointChe).icon(che);
//创建OverlayOptions的集合(把起点终点和车辆绘制在地图上)
List<OverlayOptions> options = new ArrayList<>();
options.add(optionQi);
options.add(optionZhong);
mMoveMarker = (Marker) mBaiduMap.addOverlay(markerOptions);
//(绘制在地图上)
mBaiduMap.addOverlays(options);
LatLngBounds mBaiduLatLngBounds = tempBounds.build();
mBaiduMap.animateMapStatus(MapStatusUpdateFactory.newLatLngBounds(mBaiduLatLngBounds));
//循环移动车辆位置
moveLooper();
}
/**
* 计算x方向每次移动的距离
*/
private double getXMoveDistance(double slope) {
if (slope == Double.MAX_VALUE) {
return DISTANCE;
}
return Math.abs((DISTANCE * slope) / Math.sqrt(1 + slope * slope));
}
/**
* 循环进行移动逻辑
*/
public void moveLooper() {
//为了不阻塞ui线程所以开启子线程
new Thread(new Runnable() {
@Override
public void run() {
//死循环
while (true) {
for (int i = 0; i < latLngs.size() - 1; i++) {
final LatLng startPoint = latLngs.get(i);
final LatLng endPoint = latLngs.get(i + 1);
mMoveMarker
.setPosition(startPoint);
mHandler.post(new Runnable() {
@Override
public void run() {
// refresh marker's rotate
if (mMapView == null) {
return;
}
mMoveMarker.setRotate((float) getAngle(startPoint,
endPoint));
}
});
double slope = getSlope(startPoint, endPoint);
// 是不是正向的标示
boolean isReverse = (startPoint.latitude > endPoint.latitude);
double intercept = getInterception(slope, startPoint);
double xMoveDistance = isReverse ? getXMoveDistance(slope) :
-1 * getXMoveDistance(slope);
for (double j = startPoint.latitude; !((j > endPoint.latitude) ^ isReverse);
j = j - xMoveDistance) {
LatLng latLng;
if (slope == Double.MAX_VALUE) {
latLng = new LatLng(j, startPoint.longitude);
} else {
latLng = new LatLng(j, (j - intercept) / slope);
}
final LatLng finalLatLng = latLng;
mHandler.post(new Runnable() {
@Override
public void run() {
if (mMapView == null) {
return;
}
mMoveMarker.setPosition(finalLatLng);
latLngs2.add(startPoint);
latLngs2.add(finalLatLng);
mPolyline2.setPoints(latLngs2);
}
});
try {
Thread.sleep(TIME_INTERVAL);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}).start();
}
/**
* 根据两点算取图标转的角度
*/
private double getAngle(LatLng fromPoint, LatLng toPoint) {
double slope = getSlope(fromPoint, toPoint);
if (slope == Double.MAX_VALUE) {
if (toPoint.latitude > fromPoint.latitude) {
return 0;
} else {
return 180;
}
}
float deltAngle = 0;
if ((toPoint.latitude - fromPoint.latitude) * slope < 0) {
deltAngle = 180;
}
double radio = Math.atan(slope);
return 180 * (radio / Math.PI) + deltAngle - 90;
}
/**
* 根据点和斜率算取截距
*/
private double getInterception(double slope, LatLng point) {
return point.latitude - slope * point.longitude;
}
/**
* 算斜率
*/
private double getSlope(LatLng fromPoint, LatLng toPoint) {
if (toPoint.longitude == fromPoint.longitude) {
return Double.MAX_VALUE;
}
return ((toPoint.latitude - fromPoint.latitude) / (toPoint.longitude - fromPoint.longitude));
}