这个案例是基于高德的路径规划案例写的,高德路径规划案例源码地址:
我将用于绘制路径的RideRouteOverlay中的添加路径点和添加起始结束点的代码给去掉了,后面我们将会自定义根据需要添加Mark点。
为了显示我们当前的位置,我们需要进行定位获取,这里的定位代码中有一些其他功能的代码,我会在下面进行说明
//开始定位
private void initLocation() {//1 定位 2 定位+开始服务
aMap.clear();
AMapLocationClient mLocationClient = new AMapLocationClient(this);
AMapLocationClientOption option = new AMapLocationClientOption();
option.setLocationMode(AMapLocationClientOption.AMapLocationMode.Hight_Accuracy);
option.setOnceLocation(true);
mLocationClient.setLocationOption(option);
mLocationClient.setLocationListener(new AMapLocationListener() {
@Override
public void onLocationChanged(AMapLocation aMapLocation) {
if (aMapLocation != null) {
if (aMapLocation.getErrorCode() == 0) {
//保存当前的位置
mCurrentLatitude = aMapLocation.getLatitude();
mCurrentLongitude = aMapLocation.getLongitude();
//根据地址距当前的距离对地址进行排序
latLonPoints = SortPathUtil.sortStraightPath(new LatLonPoint(aMapLocation.getLatitude(), aMapLocation.getLongitude()), latLonPoints);
initAddressList();
Log.e("排序", latLonPoints.size() + "");
//根据所有的点计算出边界点的区域,然后展示最小包裹所有点的区域
try {
ArrayList<LatLonPoint> boundsPoints = new ArrayList<>();
for (int i = 0; i < latLonPoints.size(); i++) {
AddressPointBean latLonPoint = latLonPoints.get(i);
if(i == 0){
boundsPoints.add(latLonPoint.getLatLonPoints()[0]);
}
boundsPoints.add(latLonPoint.getLatLonPoints()[1]);
}
LatLngBounds latLngBounds = AMapUtil.getLatLngBounds(boundsPoints);
aMap.animateCamera(CameraUpdateFactory
.newLatLngBounds(latLngBounds, 50));
} catch (Throwable e) {
e.printStackTrace();
}
drawLine();
}
}
}
});
mLocationClient.startLocation();
}
当定位完成之后,我们就可以根据当前的位置与地址列表的距离进行规划了,这里我是使用的递归进行规划的,逻辑很简单就是利用循环寻找距离当前点最近的一个点(假如地址列表中有置顶的地址除外,会直接用置顶的地址为第一个点),然后再以这个最近的点为基准继续寻找最近的点依次类推得出一个根据距离排好序的地址列表:
public class SortPathUtil {
/**
* 对点进行距离排序,返回两两需要绘制距离的点数组的集合
* @return
*/
public static List<AddressPointBean> sortStraightPath(LatLonPoint currentLocation,List<AddressPointBean> points){
//计算当前用户与集合中点的距离,找出最短距离
List<AddressPointBean> resultPoints = new ArrayList<>();
getShortDistancePointArgs(currentLocation,points,resultPoints);
Log.e("集合",resultPoints.toString());
return resultPoints;
}
private static void getShortDistancePointArgs(LatLonPoint flagPoint,List<AddressPointBean> points,List<AddressPointBean> resultPoints){
LatLonPoint[] shortPointArgs = new LatLonPoint[2];
AddressPointBean shortAddress = null;
float distance = -1;
LatLng latLngStart = new LatLng(flagPoint.getLatitude(),flagPoint.getLongitude());
LatLng latLngEnd;
//先看看有没有需要置顶的点
boolean isHaveShowTop = false;
for (AddressPointBean addressPointBean : points) {
if(addressPointBean.isShowTop()){
isHaveShowTop = true;
shortAddress = addressPointBean;
break;
}
}
//没有置顶的点就按常规排序
if(!isHaveShowTop){
for (AddressPointBean addressPointBean : points) {
LatLonPoint point = addressPointBean.getCurrentPoint();
latLngEnd = new LatLng(point.getLatitude(),point.getLongitude());
float tempDistance = AMapUtils.calculateLineDistance(latLngStart, latLngEnd);
//找出最短的距离
if(distance == -1){
distance = tempDistance;
shortAddress = addressPointBean;
Log.e("最短距离A",tempDistance + "");
}else if(tempDistance < distance){
distance = tempDistance;
shortAddress = addressPointBean;
Log.e("最短距离B",tempDistance + "");
}
Log.e("最短距离",tempDistance + "");
}
}
shortPointArgs[0] = flagPoint;
shortPointArgs[1] = shortAddress.getCurrentPoint();
AddressPointBean addressPointBean = new AddressPointBean();
addressPointBean.setLatLonPoints(shortPointArgs);
addressPointBean.setAddressName(shortAddress.getAddressName());
addressPointBean.setShowTop(false);
addressPointBean.setCurrentPoint(shortAddress.getCurrentPoint());
resultPoints.add(addressPointBean);
points.remove(shortAddress);
if(points.size() > 0){
getShortDistancePointArgs(shortAddress.getCurrentPoint(),points,resultPoints);
}
}
}
这里我为了操作方便就创建了一个地址的实体类:
/**
* 存地址列表的实体类
*/
public class AddressPointBean {
private LatLonPoint[] latLonPoints;//用于规划路径的点数组
private LatLonPoint currentPoint;//用于保存当前点的信息
private String addressName;
private boolean isShowTop;//为真时自动排在第一位
public AddressPointBean() {
}
public AddressPointBean(LatLonPoint currentPoint, String addressName) {
this.currentPoint = currentPoint;
this.addressName = addressName;
}
public LatLonPoint getCurrentPoint() {
return currentPoint;
}
public void setCurrentPoint(LatLonPoint currentPoint) {
this.currentPoint = currentPoint;
}
public LatLonPoint[] getLatLonPoints() {
return latLonPoints;
}
public void setLatLonPoints(LatLonPoint[] latLonPoints) {
this.latLonPoints = latLonPoints;
}
public String getAddressName() {
return addressName;
}
public void setAddressName(String addressName) {
this.addressName = addressName;
}
public boolean isShowTop() {
return isShowTop;
}
public void setShowTop(boolean showTop) {
isShowTop = showTop;
}
}
当然案例中使用的地址列表中的所有的点都是我自己手动添加的测试数据,真实环境中可以从接口获得:
/**
* 预处理地址列表(相当于真实环境的获取接口的地址列表)
*/
private void preAddress() {
//模拟的多个位置
latLonPoints = new ArrayList<>();
latLonPoints.add(new AddressPointBean(new LatLonPoint(36.682871, 117.123232),"济南市历下区工业南路59号中铁财智中心1号楼103室"));
latLonPoints.add(new AddressPointBean(new LatLonPoint(36.689582, 117.129498),"济南市历下区盛世花城(康虹路南)"));
latLonPoints.add(new AddressPointBean(new LatLonPoint(36.684248, 117.139325),"济南市历下区天辰大街978号"));
}
规划完点后我们需要将规划后的路径在地图上展示出来,代码如下:
private void drawLine(){
//先添加当前位置的标志
AddressPointBean addressPointUser = latLonPoints.get(0);
aMap.addMarker((new MarkerOptions())
.position(new LatLng(addressPointUser.getLatLonPoints()[0].getLatitude(), addressPointUser.getLatLonPoints()[0].getLongitude())).icon(BitmapDescriptorFactory.fromResource(R.drawable.ic_usermark)));
for (int i = 0; i < latLonPoints.size(); i++) {
final AddressPointBean addressPointBean = latLonPoints.get(i);
//添加订单顺序标记
TextView textView = new TextView(this);
textView.setBackgroundResource(R.drawable.shape_numbg);
textView.setTextColor(Color.WHITE);
textView.setGravity(Gravity.CENTER);
textView.setWidth(100);
textView.setHeight(100);
textView.setTextSize(20);
textView.setText((i + 1) + "");
aMap.addMarker((new MarkerOptions())
.position(new LatLng(addressPointBean.getLatLonPoints()[1].getLatitude(), addressPointBean.getLatLonPoints()[1].getLongitude())).icon(BitmapDescriptorFactory.fromView(textView)));
//路径规划
searchRouteResult(addressPointBean.getLatLonPoints(), ROUTE_TYPE_RIDE, RouteSearch.RIDING_DEFAULT);
}
}
/**
* 开始搜索路径规划方案
*/
public synchronized void searchRouteResult(LatLonPoint[] points, int routeType, int mode) {
final RouteSearch.FromAndTo fromAndTo = new RouteSearch.FromAndTo(
points[0], points[1]);
if (routeType == ROUTE_TYPE_RIDE) {// 骑行路径规划
RideRouteQuery query = new RideRouteQuery(fromAndTo, mode);
mRouteSearch.calculateRideRouteAsyn(query);// 异步路径规划骑行模式查询
}
}
然后在高德的路径规划回调方法中使用RideRouteOverlay进行绘制:
@Override
public void onRideRouteSearched(final RideRouteResult result, final int errorCode) {
if (errorCode == AMapException.CODE_AMAP_SUCCESS) {
if (result != null && result.getPaths() != null) {
if (result.getPaths().size() > 0) {
mRideRouteResult = result;
final RidePath ridePath = mRideRouteResult.getPaths()
.get(0);
RideRouteOverlay rideRouteOverlay = new RideRouteOverlay(
RideRouteActivity.this, aMap, ridePath,
mRideRouteResult.getStartPos(),
mRideRouteResult.getTargetPos());
// rideRouteOverlay.removeFromMap();
rideRouteOverlay.addToMap();
// rideRouteOverlay.zoomToSpan();
} else if (result != null && result.getPaths() == null) {
ToastUtil.show(mContext, R.string.no_result);
}
} else {
ToastUtil.show(mContext, R.string.no_result);
}
} else {
ToastUtil.showerror(mContext, errorCode);
}
}
这里我们也需要将高德源码中的removeFromMap和zoomToSpan方法都注释掉,否则会出现展示区域不能包裹所有点的问题
地址列表的展示我直接使用了一个RecyclerView展示的,其中使用的BaseQuickAdapter是一个三方的适配器:
private void initAddressList() {
if(baseQuickAdapter == null){
baseQuickAdapter = new BaseQuickAdapter<AddressPointBean, BaseViewHolder>(R.layout.item_address, latLonPoints) {
@Override
protected void convert(BaseViewHolder helper, AddressPointBean item) {
TextView tvNum = helper.getView(R.id.tv_num);
TextView tvAddress = helper.getView(R.id.tv_address);
TextView tvDistance = helper.getView(R.id.tv_distance);
tvNum.setText(helper.getLayoutPosition() + "");
tvAddress.setText(item.getAddressName());
LatLng currentLatLng = new LatLng(mCurrentLatitude, mCurrentLongitude);
LatLng addresstLatLng = new LatLng(item.getCurrentPoint().getLatitude(), item.getCurrentPoint().getLongitude());
float distance = AMapUtils.calculateLineDistance(currentLatLng,addresstLatLng);
if(distance >= 1000){
distance = distance / 1000;
tvDistance.setText("距离:"+distance + "km");
}else {
tvDistance.setText("距离:"+distance + "m");
}
}
};
baseQuickAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
@Override
public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
//将点击的地址置顶
latLonPoints.get(position).setShowTop(true);
//重新绘制路径及点的位置
initLocation();
//点击后自动显示地图
slFloatcontainer.scrollToOpen();
}
});
View headerView = View.inflate(this, R.layout.view_header, null);
baseQuickAdapter.addHeaderView(headerView);
rvAddresslist.setLayoutManager(new LinearLayoutManager(this));
rvAddresslist.setAdapter(baseQuickAdapter);
}else {
baseQuickAdapter.setNewData(latLonPoints);
baseQuickAdapter.notifyDataSetChanged();
}
}
为了使规划的路径看的更清楚直观,我们需要在包裹所有点的前提条件下展示大的缩放度,也就是定位逻辑中的这段代码:
//根据所有的点计算出边界点的区域,然后展示最小包裹所有点的区域
try {
ArrayList<LatLonPoint> boundsPoints = new ArrayList<>();
for (int i = 0; i < latLonPoints.size(); i++) {
AddressPointBean latLonPoint = latLonPoints.get(i);
if(i == 0){
boundsPoints.add(latLonPoint.getLatLonPoints()[0]);
}
boundsPoints.add(latLonPoint.getLatLonPoints()[1]);
}
LatLngBounds latLngBounds = AMapUtil.getLatLngBounds(boundsPoints);
aMap.animateCamera(CameraUpdateFactory
.newLatLngBounds(latLngBounds, 50));
} catch (Throwable e) {
e.printStackTrace();
}
其中getLatLngBounds方法如下,逻辑很简单就是在所有点中计算出两个边缘点作为展示区域
/**
* 获取点集合的范围
* @return
* @param pointList
*/
public static LatLngBounds getLatLngBounds(List<LatLonPoint> pointList) {
LatLngBounds.Builder b = LatLngBounds.builder();
LatLonPoint lngMinPoint = getLngMinPoint(pointList);
LatLonPoint lngMaxPoint = getLngMaxPoint(pointList);
LatLng latLngLngMin = new LatLng(lngMinPoint.getLatitude(), lngMinPoint.getLongitude());
LatLng latLngLngMax = new LatLng(lngMaxPoint.getLatitude(), lngMaxPoint.getLongitude());
double distanceH = lngMaxPoint.getLongitude() - lngMinPoint.getLongitude();
LatLonPoint latMinPoint = getLatMinPoint(pointList);
LatLonPoint latMaxPoint = getLatMaxPoint(pointList);
LatLng latLngLatMin = new LatLng(latMinPoint.getLatitude(), latMinPoint.getLongitude());
LatLng latLngLatMax = new LatLng(latMaxPoint.getLatitude(), latMaxPoint.getLongitude());
double distanceV = latMaxPoint.getLatitude() - latMinPoint.getLatitude();
if(distanceH > distanceV){
b.include(latLngLngMin);
b.include(latLngLngMax);
}else {
b.include(latLngLatMin);
b.include(latLngLatMax);
}
return b.build();
}
private static LatLonPoint getLngMinPoint(List<LatLonPoint> pointList) {
LatLonPoint latLonPointLngMin = pointList.get(0);
for (LatLonPoint latLonPoint : pointList) {
if (latLonPointLngMin.getLongitude() > latLonPoint.getLongitude()) {
latLonPointLngMin = latLonPoint;
}
}
return latLonPointLngMin;
}
private static LatLonPoint getLngMaxPoint(List<LatLonPoint> pointList) {
LatLonPoint latLonPointLngMax = pointList.get(0);
for (LatLonPoint latLonPoint : pointList) {
if (latLonPointLngMax.getLongitude() < latLonPoint.getLongitude()) {
latLonPointLngMax = latLonPoint;
}
}
return latLonPointLngMax;
}
private static LatLonPoint getLatMinPoint(List<LatLonPoint> pointList) {
LatLonPoint latLonPointLatMin = pointList.get(0);
for (LatLonPoint latLonPoint : pointList) {
if (latLonPointLatMin.getLatitude() > latLonPoint.getLatitude()) {
latLonPointLatMin = latLonPoint;
}
}
return latLonPointLatMin;
}
private static LatLonPoint getLatMaxPoint(List<LatLonPoint> pointList) {
LatLonPoint latLonPointLatMax = pointList.get(0);
for (LatLonPoint latLonPoint : pointList) {
if (latLonPointLatMax.getLatitude() < latLonPoint.getLatitude()) {
latLonPointLatMax = latLonPoint;
}
}
return latLonPointLatMax;
}
8、增加点击列表改变路径规划顺序
最后我们增加当点击列表某个地址的时候使这个被点击的地址置顶,也就是加载地址列表中的这段代码:
baseQuickAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
@Override
public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
//将点击的地址置顶
latLonPoints.get(position).setShowTop(true);
//重新绘制路径及点的位置
initLocation();
//点击后自动显示地图
slFloatcontainer.scrollToOpen();
}
});
注意:这里我没加动态获取权限的代码,因此需要自行加上或者手动在应用管理打开位置权限。
真诚点赞 诚不我欺~