ps:又到了周五,才有时间来写点博客记录下,这篇文章的所描述的内容是我2017年后 做的一个功能,也是我们的产品经理结合 航迹规划 以及我们所提供的 能做到的一些地图开发结合,做的一个自创性的 开发吧。其实开始做的时候挺不自信的,因为第一,地图sdk 并不是非常了解,再说,里面有好多效果和功能 ,主要是一个动态修改航点和航线这个效果,在评估的时候,着实让我觉得头疼。而且领导还说项目很急,让我一周把航迹规划和电子围栏的开发 做完,我和ios 当初可是评估后给了 3个星期的工作日的。他要我们一周搞完,心里挺虚的。 本身博主就是个菜鸡,毕业才半年,虽说在公司实习到现在有一年了,但是毕竟改变不了菜的现实。没办法,顶头干吧。在开发中,越搞越happay ,常常加班到11点,觉得蛮有意思的,显然不是纯粹的使用地图的简单功能来完成开发的,而是有点难度的。听意外的,4个工作日,就把航迹规划和电子围栏 搞完了。满有成就感的。
博主以前是学文科的,废话有点多,不要见怪。整个效果展示的比较长,但是呢,gif 又只能上传2M 的图,无奈只能一个一个截取了。fps 设置的也有点低,没办法,gif 2M 实在太小。将就看吧。
1.
2.
3.
一:这次只展示 航迹规划这一个项目,电子围栏,我已经在之前文章中写过了。
二:本次功能要点我做个简单的阐述。
①.要分清楚 你需要建立几个集合 分别是:lList<Latlng>,List<Marker>,List<PolyLine>,代表的分别是,坐标点,标记点,航线。
②.在UI 给的图片上 绘制 标号,技术好的应该都会,我下面会把代码贴出来
③.就是吧Marker的.draggable(true),前提 先吧拖拽功能打开。才可以操作
④重点是这个 线拖拽的问题,分别位2种情况
1)例如图一 在没有2 出现的情况下只有一种情况,拖拽 marker 1 将 线一拖动,
2)例如2 在拖拽2的时候会牵扯到 2根线,2根线要同步刷新。
3)额外说的一个,我的电子围栏中 更复杂一点,比如第一个点生成。 逻辑都和这个一样,但是在最后一根线的时候,他要围城一个围栏,在没围成之前,拖拽marker 1的时候 只有一根线,拖拽2 会有2根,拖拽最后一个Marker 也只有一根,但是一旦确认 围栏 形成,最后一个Marker会和第一个Marker 产生一条线,这时候在拖拽 1和最后一个marker 就需要联动,所有的点都是 2根线 在动。这个 就从代码上没什么技术含量,但是逻辑上 需要思考好多。这次就不说这个,我下面会把代码贴出来。
开始贴代码了。
重点的几个说完 就直接把所有的贴出来。
private ArrayList<LatLng> mPfLatLngList = new ArrayList<>(); // latlng的数量比marker的数量多一,因为当前位置坐标也记录下来了
private ArrayList<PathPlanBean> uploadList = new ArrayList<>(); //上传航迹规划列表的集合
private ArrayList<Marker> mPfMarkerList = new ArrayList<>();
private ArrayList<Polyline> mPfPolylineList = new ArrayList<>();
/**
* Marker地图点击监听事件
*
* @param latLng
*/
@Override
public void onMapClick(LatLng latLng) {
LatLng curLatlng = latLng;
// 航迹规划
if (isOpenFlightRoutePlan && mPfMarkerList.size() < 50 && isWayPointMode_state == GduConfig.FpClose) {
LatLng lastLatlng;
if (!mPfLatLngList.isEmpty()) { // 这里判断不为空,是保证已经 有飞机的坐标点 已经添加
if (AMapUtils.calculateLineDistance(currentphoneLatlng, curLatlng) > fp_validityRange) {
txhToast.show(getString(R.string.Label_Map_FlightRoute_flightpoit_invalidation));
} else {
int tagNum = mPfLatLngList.size();//Marker 数_标
mPfMarkerList.add(mapMarkerTool.addPfMarker(curLatlng, tagNum));
mPfLatLngList.add(curLatlng);
mIv_Fp_state.setImageResource(R.drawable.bg_map_fp_go);
if (tagNum == 1) {
LatLng droneLat = mPfLatLngList.get(0);
mPfPolylineList.add(mapMarkerTool.addPolyDottedLine(droneLat, curLatlng));
mPfFlightDisList.add(calculateFlightDist(droneLat, curLatlng));
} else if (tagNum > 1) {
lastLatlng = mPfLatLngList.get(mPfLatLngList.size() - 2);
mPfPolylineList.add(mapMarkerTool.addPolyDottedLine(lastLatlng, curLatlng));
mPfFlightDisList.add(calculateFlightDist(lastLatlng, curLatlng));
}
setEraserState(true);//有Marker增加时候
}
}
} else if (isOpenFlightRoutePlan && mPfMarkerList.size() == 50 && isWayPointMode_state == GduConfig.FpClose) {
txhToast.show(getString(R.string.Label_Map_FlightRoute_flightpoit_overflow));
} else if (isOpenFlightRoutePlan && mPfMarkerList.size() < 50 && isWayPointMode_state == GduConfig.FpGo) {
// 即当开始航迹规划,在点击地图,无响应,不增加点
}
在地图上增加点 必须在onMapClick 中操作,在onMapClick 获取最基本的Latlng 数据,并绘制 航线。
add Marker
/**
* <p>根据经纬度往地图上添加marker</p>
* <p>[航迹规划]</p>
*
* @param lagLng 当前点击的 下的坐标
* @param markerNum 计数
* @return
*/
public Marker addPfMarker(LatLng lagLng, int markerNum) {
MarkerOptions options= options = new MarkerOptions().draggable(true);
options.anchor(0.5f, 1f)
.position(lagLng)
.icon(BitmapDescriptorFactory.fromBitmap(getBitMap(markerNum)));
return aMap.addMarker(options);
}
add PolyLine
/**
* <p>绘制两点之间线段</p>
* <p>虚线</p>
*
* @param beginlatLng 起点
* @param endLatLng 终点
* @return
*/
public Polyline addPolyDottedLine(LatLng beginlatLng, LatLng endLatLng) {
PolylineOptions options = new PolylineOptions();
options.add(beginlatLng, endLatLng)
.width(10)
.color(Color.rgb(255, 121, 24))
.setDottedLine(true);// 虚线
return aMap.addPolyline(options);
}
计算距离:
/**
* <p>计算总的飞行距离</p>
*
* @param beginlatLng
* @param endLatLng
*/
private float calculateFlightDist(LatLng beginlatLng, LatLng endLatLng) {
float onceDist = AMapUtils.calculateLineDistance(beginlatLng, endLatLng);
AllFlightDist += onceDist;
DecimalFormat df = new DecimalFormat("##.#");
mTv_flightdis.setText(df.format(AllFlightDist) + StringUtils.getUnit());
return Float.parseFloat(df.format(AllFlightDist));
}
这是基本的 添加数据 在地图上显示,下面就让他来动起来吧。
设置监听。
private void initGuideMap(AMap aMap) {
// 设置marker可拖拽事件监听器
aMap.setOnMarkerDragListener(this);
// 设置显示marker信息监听,要想显示信息必须注册
// aMap.setInfoWindowAdapter(this);
aMap.setOnMarkerClickListener(this);
// aMap.setOnInfoWindowClickListener(this);
// 初始化Marker配置
options = new MarkerOptions().draggable(true);
}
监听回调:
/**
* <p>Marker 拖拽开始监听</p>
* <li>增加振荡器</li>
* <li>获取当前Marker 的起始坐标并保存</li>
* <li>作用:当不满足条件的时候,就可以使用起点坐标</li>
*
* @param marker
*/
private LatLng curMarkerPosition;
@Override
public void onMarkerDragStart(Marker marker) {
Vibrator vibrator = (Vibrator) context.getSystemService(Service.VIBRATOR_SERVICE);
vibrator.vibrate(150);
if (isPfMode == GduConfig.FpMode) {
for (int i = 0; i < mPfMarkerList.size(); i++) {
if (marker.equals(mPfMarkerList.get(i))) {
curMarkerPosition = mPfLatLngList.get(i + 1);
}
}
} else if (isPfMode == GduConfig.ErMode) {
for (int i = 0; i < mErMarkerList.size(); i++) {
if (marker.equals(mErMarkerList.get(i))) {
curMarkerPosition = mErLatLngList.get(i);
}
}
}
}
/**
* <p>拖拽过程中的监听</p>
*
* @param marker
*/
@Override
public void onMarkerDrag(Marker marker) {
drawTrackLine(marker);// 正常的Marker监听中
}
/**
* <p>拖拽完成后的监听</p>
* <li>当状态为:航迹规划进行中/电子围栏已启动 ,拖动点回归原点</li>
* <li>拖拽完成后检查是否超过2000的判定</li>
*
* @param marker
*/
@Override
public void onMarkerDragEnd(Marker marker) {
// 模式开启,飞机在执行 指令操作,所有的点都不能生效
if (isWayPointMode_state == GduConfig.FpGo || isWayPointMode_state == GduConfig.FpGoOn || isElectronicRail_state == GduConfig.ErStart) {
BackDraggablePoint(marker);// 当前是:航迹规划状态/电子围栏状态
return;
}
if (isPfMode == GduConfig.FpMode && AMapUtils.calculateLineDistance(mPfLatLngList.get(0), marker.getPosition()) > fp_validityRange) {
txhToast.show(context.getString(R.string.Label_Map_FlightRoute_flightpoit_invalidation));
BackDraggablePoint(marker);// 就是超过2000米 的判断
}
}
继续 上一个 代码中的方法
/**
* <p>画航线</p>
* <li>第一:分航迹规划模式和围栏模式</li>
* <li>第二:围栏模式分:围栏模式已经启动 -实线情况,围栏模式未启动 -虚线情况</li>
* <li>第三:围栏模式拖拽:分几个特殊点 ① 起点 ②中间点 ③ 终点 </li>
* <li>第四:围栏模式拖拽:没启动围栏模式前,是没有最后一根线的,也就是说 5个点四根线 可拖拽的效果也是要分情况的 </li>
* <li>不仅在拖拽的时候要实况改变预设航线,</li>
* <li>还要在拖拽结束的时候判断是否满足航线规划的要求</li>
* <li>满足就按照用户的定义的来,不满足就要回到起始点</li>
*/
private void drawTrackLine(Marker marker) {
if (isPfMode == GduConfig.FpMode) { // 航迹规划
boolean isExceed = AMapUtils.calculateLineDistance(mPfLatLngList.get(0), marker.getPosition()) > fp_validityRange;
outofrangeCircle(isExceed);
for (int i = 0; i < mPfMarkerList.size(); i++) {
if (marker.equals(mPfMarkerList.get(i))) {
mPfLatLngList.set(i + 1, marker.getPosition()); // 第一个点是无人机的点,所以要 +1 ;
mPfPolylineList.get(i).remove();
mPfPolylineList.set(i,
addPolyDottedLine(mPfLatLngList.get(i), //虚线
mPfLatLngList.get(i + 1)));
if (i < mPfMarkerList.size() - 1) {
mPfPolylineList.get(i + 1).remove();
mPfPolylineList.set(i + 1,
addPolyDottedLine(mPfLatLngList.get(i + 1),//虚线
mPfLatLngList.get(i + 2)));
}
updateFlightDisEvent(i, marker);//实况更新飞行距离的事件
return;
}
}
} else if (isPfMode == GduConfig.ErMode) { // 电子围栏
for (int i = 0; i < mErMarkerList.size(); i++) {
if (marker.equals(mErMarkerList.get(i))) {
if (mErMarkerList.size() > 1) {
mErLatLngList.set(i, marker.getPosition());// 没有无人机的点,所以就按照第0点来
// 一根线 (点的后面那根)
if (isElectronicRail_state == GduConfig.ErStart) { // 已经开启电子围栏模式
if (i == mErMarkerList.size() - 1) { // 【特殊点】移动的是最后一个点就是【起点】,手动添加的 Polyline
mErPolylineList.get(i).remove();
mErPolylineList.set(i,
addPolySolidLine(mErLatLngList.get(i),//实线
mErLatLngList.get(0)));
} else { // 移动的不是最后一个点
mErPolylineList.get(i).remove();
mErPolylineList.set(i,
addPolySolidLine(mErLatLngList.get(i),//实线
mErLatLngList.get(i + 1)));
}
} else { // 没有开启电子围栏
if (i < mErPolylineList.size()) {
mErPolylineList.get(i).remove();
mErPolylineList.set(i,
addPolyDottedLine(mErLatLngList.get(i),//虚线
mErLatLngList.get(i + 1)));
}
}
// 二根线 (点的后前面那根)
if (isElectronicRail_state == GduConfig.ErStart) { // 已经开启电子围栏模式
if (i == 0) { //【特殊点】 移动终点
mErPolylineList.get(mErPolylineList.size() - 1).remove();
mErPolylineList.set(mErPolylineList.size() - 1,
addPolySolidLine(mErLatLngList.get(i), //实线
mErLatLngList.get(mErLatLngList.size() - 1)));
} else {
mErPolylineList.get(i - 1).remove();
mErPolylineList.set(i - 1,
addPolySolidLine(mErLatLngList.get(i),//实线
mErLatLngList.get(i - 1)));
}
} else {
if (i > 0) {
mErPolylineList.get(i - 1).remove();
mErPolylineList.set(i - 1,
addPolyDottedLine(mErLatLngList.get(i),//虚线
mErLatLngList.get(i - 1)));
}
}
return;
}
}
}
}
}
继续上一个代码的方法
/** * <p>回到拖拽的起点</p> * <li>当不满足要求的时候,就会回到起始点</li> * * @param marker */ private void BackDraggablePoint(Marker marker) { marker.setPosition(curMarkerPosition); drawTrackLine(marker);// 不满足要求 }
然后就是 有效域的判定
我其实是事先做了2个 有效域 的圆形,根据 地图的 距离计算,当大于2000 就隐藏第一个圆形 显示下一个圆形,如果第二个没有
就新建,如果存在就 显示,
boolean isExceed = AMapUtils.calculateLineDistance(mPfLatLngList.get(0), marker.getPosition()) > 2000;
有效域警告域circle = aMap.addCircle(new CircleOptions() .center(latLng) .radius(fp_validityRange) .fillColor(Color.argb(26, 0, 120, 255)) // 填充域颜色 .strokeColor(Color.argb(102, 0, 160, 233))// 边框颜色 .strokeWidth(2));
/** * <p>是否超出有效域,超出2000 有效域变色</p> * <p>是超出范围的圈/p> */ public void outofrangeCircle(boolean isExceed) { if (warning_circle == null) { warning_circle = aMap.addCircle(new CircleOptions() .center(currentphoneLatlng) .radius(fp_validityRange) .fillColor(Color.argb(26, 255, 199, 65)) // 填充域颜色 .strokeColor(Color.argb(102, 255, 199, 65))// 边框颜色 .strokeWidth(2)); } if (isExceed) { warning_circle.setVisible(true); normal_circle.setVisible(false); } else { normal_circle.setVisible(true); warning_circle.setVisible(false); } }
基本 的重点都讲完了,下面是我完整的 java 文件GuideMapFragment.javapackage com.gdu.mvp_view.flightRouteplan; import android.app.Fragment; import android.graphics.Color; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.support.annotation.Nullable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; import com.amap.api.location.AMapLocation; import com.amap.api.location.AMapLocationClient; import com.amap.api.location.AMapLocationClientOption; import com.amap.api.location.AMapLocationListener; import com.amap.api.maps.AMap; import com.amap.api.maps.AMapOptions; import com.amap.api.maps.AMapUtils; import com.amap.api.maps.CameraUpdateFactory; import com.amap.api.maps.LocationSource; import com.amap.api.maps.TextureMapView; import com.amap.api.maps.UiSettings; import com.amap.api.maps.model.BitmapDescriptorFactory; import com.amap.api.maps.model.Circle; import com.amap.api.maps.model.CircleOptions; import com.amap.api.maps.model.LatLng; import com.amap.api.maps.model.Marker; import com.amap.api.maps.model.MyLocationStyle; import com.amap.api.maps.model.Polyline; import com.gdu._enum.ConnStateEnum; import com.gdu.beans.PathPlanBean; import com.gdu.config.GduConfig; import com.gdu.config.GlobalVariable; import com.gdu.config.UavStaticVar; import com.gdu.event.PfMostDisEvent; import com.gdu.mvp_view.application.GduApplication; import com.gdu.mvp_view.flightRouteplan.helper.GPSTranslateGuide; import com.gdu.mvp_view.flightRouteplan.helper.UpDroneCurLocation; import com.gdu.phonedrone.R; import com.gdu.socket.GduFrame; import com.gdu.socket.GduMapComm; import com.gdu.socket.GduSocketConfig; import com.gdu.socket.SocketCallBack; import com.gdu.util.AnimationUtils; import com.gdu.util.BBLog; import com.gdu.util.DialogUtils; import com.gdu.util.SPUtils; import com.gdu.util.StringUtils; import com.gdu.util.ToastFactory; import com.gdu.util.YhLog; import com.gdu.util.dialog.BlackGeneralDialog; import com.gdu.util.dialog.GeneralDialog; import com.gdu.views.AngelaTXHToast; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Timer; import java.util.TimerTask; import de.greenrobot.event.EventBus; /** * Created by shangbeibei on 2017/4/20. * <p>航迹规划--高德地图</p> */ public class GuideMapFragment extends Fragment implements AMap.OnMapLoadedListener , View.OnClickListener , LocationSource // 回调监听 , AMapLocationListener //定外监听 , AMap.OnMapClickListener { // init GuideMapFragment mapview private AMap aMap; // init MapUtils private GuideMapMarkerTool mapMarkerTool; // init 定位样式,定位蓝点 private MyLocationStyle myLocationStyle; //声明AMapLocationClient类对象 public AMapLocationClient mLocationClient = null; //声明mLocationOption对象 public AMapLocationClientOption mLocationOption = null; //重要的地图工具,有 比例尺,有logo位置 有 缩放按钮, 指南针。滑动手势,缩放手势 private UiSettings mUiSettings; private OnLocationChangedListener mListener;//定位成功回调 // 定位当前手机的位置 private LatLng currentphoneLatlng; // 有效域 圆 private Circle circle; // 无效域 圆 private Circle warning_circle; // 航迹规划 【有效域范围】半径 public static int fp_validityRange = 2000; // init GuideMapFragment view private static GuideMapFragment guideMapFragment; private View view; // 高德地图 private TextureMapView mMapView; // 地图控制台 private RelativeLayout mRl_MapControlPanel; // 清除航线规划 private ImageView mIv_fp_cleanroutePlan; // 电子围栏 private ImageView mIv_electronicRail_on2off; // 航迹规划的开关 private ImageView mIv_flightPlan_on2off; // 隐藏控制台 iocn private ImageView mIv_hide_controlpanel; // 显示控制台 icon private ImageView mIv_show_controlpanel; // 地图定位回调 private ImageView mIv_fp_maplocation; // 航迹规划的状态 private ImageView mIv_Fp_state; // 电子围栏的状态 private ImageView mIv_Er_state; // 地图样式切换 button private ImageView mIv_switch_mapstyle; // 地图切换 样式 的layout 选项框 private LinearLayout mll_switch_maplayout; // (热区域)点击地图样式,普通地图 private LinearLayout mll_click_mapstyle_normal; // (热区域)点击地图样式,卫星地图 private LinearLayout mll_click_mapstyle_satellite; // 点击后,普通地图样式被选择的background 变化 private LinearLayout mll_bg_maptype_norma