Explorar o código

坐标系标定与导航

jiuling hai 8 meses
pai
achega
ce110558b1

+ 1 - 0
.env.development

@@ -6,6 +6,7 @@ ENV = 'development'
 
 # 系统/开发环境
 VUE_APP_BASE_API = '/dev-api'
+VUE_APP_PNS_MQTT_PROXY = '/robot4inspection/844727706d718717'
 VUE_APP_PNS_API = '/pns'
 
 # 路由懒加载

+ 58 - 3
src/components/OlMap/index.vue

@@ -212,6 +212,7 @@ export default {
       maxPolygonNum: 0,
       selectedFeatureId: '',  // 用于存储选中的 Feature id
       previewFeature: null, // 动态线段(位姿初始化)
+      calibrationList:[]
     }
   },
   computed: {
@@ -272,7 +273,18 @@ export default {
       },
       deep: true
     },
-    poseCalibrationIndex(index, oldVal) {
+  poseCalibrationIndex(index) {
+      if (index < 0) {
+        const id = Math.abs(index);
+        this.removeCalibrationById(id);
+      } else {
+        const newId = this.calibrationList.length + 1;
+        const point = { id: newId, x: this.robotPoseData.x, y: this.robotPoseData.y };
+        this.calibrationList.push(point);
+        this.refreshCalibrationOverlays();
+      }
+    },
+/*     poseCalibrationIndex(index, oldVal) {
       // 如果传递负值则删除标定点
       if (index < 0) {
         let id = Math.abs(index) // 求绝对值
@@ -281,7 +293,7 @@ export default {
       }
       // 添加坐标标定点
       this.addHtmlIcon(index, this.robotPoseData.x, this.robotPoseData.y, '', 'calibration');
-    },
+    }, */
     // 位姿初始化
     poseInitEnable(newVal, oldVal) {
       if (newVal) {
@@ -903,13 +915,56 @@ export default {
         element: div,
         position: [x, y],
         positioning: "center-center", // 定义div的定位方式
+        stopEvent: false, // 允许事件穿透
       });
       // 设置ID,便于后续操作
-      overlay.set("id", type + "-" + id);
+      // overlay.set("id", type + "-" + id);
+      overlay.set("id", `calibration-${id}`);
+      overlay.set("type", type);
       // 添加Overlay到地图
       this.map.addOverlay(overlay);
     },
 
+    // 删除标定点并重排编号
+    removeCalibrationById(id) {
+      // 从数据中删除并重排 id
+      this.calibrationList = this.calibrationList.filter(item => item.id !== id);
+      this.calibrationList.forEach((item, idx) => { item.id = idx + 1; });
+
+      // 刷新覆盖物
+      this.refreshCalibrationOverlays();
+    },
+    // 刷新所有标定点覆盖物
+    // 清空所有并重绘(安全删除方式)
+    refreshCalibrationOverlays() {
+      // 1) 先把当前 overlays 复制到数组中(避免在遍历时修改集合导致跳过)
+      const all = this.map.getOverlays().getArray().slice();
+
+      // 2) 找到所有需要删除的 overlay(通过 type 或 id 前缀判断)
+      const toRemove = all.filter(ov => {
+        try {
+          return ov && typeof ov.get === 'function' &&
+            (ov.get('type') === 'calibration' ||
+             (typeof ov.get('id') === 'string' && ov.get('id').startsWith('calibration-')));
+        } catch (e) {
+          return false;
+        }
+      });
+
+      // 3) 统一删除
+      toRemove.forEach(ov => this.map.removeOverlay(ov));
+
+      // 4) 重新绘制(按 this.calibrationList 顺序)
+      this.calibrationList.forEach(item => {
+        this.addHtmlIcon(item.id, item.x, item.y, '', 'calibration');
+      });
+
+      // debug: 打印当前 overlay id 列表,方便排查
+      console.log('calibrationList:', JSON.stringify(this.calibrationList));
+      console.log('overlay ids:', this.map.getOverlays().getArray().map(o => o.get && o.get('id')));
+    },
+
+
     // 删除html绘制的图标
     removeIconHtmlById(id) {
       const overlays = this.map.getOverlays(); // 获取所有 Overlay

+ 1 - 1
src/main.js

@@ -51,7 +51,7 @@ Vue.prototype.selectDictLabel = selectDictLabel
 Vue.prototype.selectDictLabels = selectDictLabels
 Vue.prototype.download = download
 Vue.prototype.handleTree = handleTree
-
+Vue.prototype.$mqttPrefix = process.env.VUE_APP_PNS_MQTT_PROXY
 // 全局组件挂载
 Vue.component('DictTag', DictTag)
 Vue.component('Pagination', Pagination)

+ 70 - 14
src/views/map/maplist/calibration.vue

@@ -12,9 +12,10 @@
           :robotPoseData="laserPositionData" 
           :poseCalibrationIndex="nowCalibId"
           :showDefaultControls="false"
+          :mapName="mapName"
         />
       </div>
-      
+      <MqttComp ref="mqtt" :topics="topics" @message-received="onMessage" />
       <!-- 地图工具条 -->
       <MapToolbar 
         class="map-toolbar"
@@ -39,6 +40,7 @@
         :calibrationList="calibrationList"
         :currentMap="currentMap"
         :panelWidth="rightPanelWidth"
+        :mapName="mapName"
         @panel-toggle="handlePanelToggle"
         @panel-resize="handlePanelResize"
         @add-calibration="addCalibration"
@@ -109,31 +111,34 @@
 import OlMap from "@/components/OlMap";
 import RightPanel from "./components/RightPanel.vue";
 import MapToolbar from "./components/MapToolbar.vue";
+import MqttComp from "@/components/Mqtt/mqttComp.vue";
 
 export default {
   name: "Calibration",
   components: {
     OlMap,
     RightPanel,
-    MapToolbar
+    MapToolbar,
+    MqttComp
   },
   data() {
     return {
+      topics:[this.$mqttPrefix + '/localization/pose'],
       // 弹出层标题
       title: "",
       calibrationList: [],
       // 激光定位数据
       laserPositionData: {
-        x: 123.45,
-        y: 678.90,
-        angle: 45.2
+        x: 0,
+        y: 0,
+        angle: 0
       },
       // gnss定位数据
       gnssPositionData: {
-        status: '卫星锁定',
-        longitude: 121.473701,
-        latitude: 31.230416,
-        angle: 45.5
+        status: '0/0',
+        longitude: 0,
+        latitude: 0,
+        angle: 0
       },
       // 当前地图
       currentMap: {
@@ -150,6 +155,8 @@ export default {
       windowWidth: window.innerWidth, // 窗口宽度
       drawerVisible: false, // 抽屉是否可见
       isFullscreen: false, // 是否全屏状态
+      mapName: this.$route.params.mapName || 'Unknown', // 当前地图名称
+      isRobotFollow: false // 是否跟随机器人
     };
   },
   computed: {
@@ -186,9 +193,8 @@ export default {
     }
   },
   mounted() {
-    const mapId = this.$route.params.mapId;
-    this.updateOlCss();
-    
+    // const mapId = this.$route.params.mapId;
+    // this.updateOlCss();
     // 监听窗口大小变化
     window.addEventListener('resize', this.handleWindowResize);
     
@@ -211,6 +217,26 @@ export default {
     document.removeEventListener('MSFullscreenChange', this.handleFullscreenChange);
   },
   methods: {
+    onMessage({ topic, message }) {
+      // console.log("收到消息:");
+      try {
+        const data = message.args[0];
+        const {xyz, rpy, blh, heading} = data.pose;
+        // 激光定位实时数据
+        this.laserPositionData.x = xyz[0];
+        this.laserPositionData.y = xyz[1];
+        this.laserPositionData.angle = rpy[2];
+        // GNSS定位实时数据
+        this.gnssPositionData.longitude = blh[1]; // 经度
+        this.gnssPositionData.latitude = blh[0]; // 纬度
+        this.gnssPositionData.angle = heading; // 航向角
+        
+        this.gnssPositionData.status = data.rtk.star+ '/' + data.rtk.status; // RTK状态
+        
+      } catch (e) {
+        console.error("解析失败:", e);
+      }
+    },
     updateOlCss() {
       this.$nextTick(() => {
         const mapStage = this.$refs.mapStage;
@@ -414,11 +440,41 @@ export default {
     },
     
     // 添加标定
+addCalibration() {
+  // 每次添加前,先根据现有列表重新排一次 id,保证连续
+  this.calibrationList = this.calibrationList.map((item, index) => {
+    return { ...item, id: index + 1 };
+  });
+
+  // 新的 id 就是当前长度+1
+  const newId = this.calibrationList.length + 1;
+  const coordinate = `${this.laserPositionData.x},${this.laserPositionData.y}`;
+  const data = { id: newId, coordinate, angle: this.laserPositionData.angle };
+
+  this.calibrationList.push(data);
+  this.nowCalibId = newId;
+},
+
+// 移除标定
+removeCalibration(id) {
+  if (this.calibrationList.length < 1) return;
+
+  // 过滤掉要删除的
+  this.calibrationList = this.calibrationList.filter(item => item.id !== id);
+
+  // 删除后重新编号,保证 id 连续
+  this.calibrationList = this.calibrationList.map((item, index) => {
+    return { ...item, id: index + 1 };
+  });
+
+  this.nowCalibId = -1; // 标记无选中
+},
+/*     // 添加标定
     addCalibration() {
       // 需要获取无人车实时坐标, 现在先写死
       this.nowCalibId = this.calibrationList.length > 0 ? this.calibrationList[this.calibrationList.length - 1].id + 1 : 1;
       let coordinate = `${this.laserPositionData.x},${this.laserPositionData.y}`
-      let data = { id: this.nowCalibId, coordinate: coordinate, angle: this.laserPositionData.angle }
+      let data = { id: this.nowCalibId, coordinate: coordinate, angle: this.laserPositionData.angle }    
       this.calibrationList.push(data)
     },
     
@@ -430,7 +486,7 @@ export default {
       this.calibrationList = this.calibrationList.filter(item => item.id !== id);
       // 删除图标
       this.nowCalibId = - id;
-    },
+    }, */
     
     // 一键标定
     executeCalibration() {

+ 2 - 2
src/views/map/maplist/components/MapToolbar.vue

@@ -21,7 +21,7 @@
       />
     </el-tooltip>
     
-    <el-tooltip content="居中到机器人" placement="right">
+    <!-- <el-tooltip content="居中到机器人" placement="right">
       <el-button 
         size="small" 
         icon="el-icon-location" 
@@ -30,7 +30,7 @@
         :disabled="!hasRobotPosition"
         class="toolbar-btn"
       />
-    </el-tooltip>
+    </el-tooltip> -->
     
     <el-tooltip :content="isFullscreen ? '退出全屏' : '全屏'" placement="right">
       <el-button 

+ 5 - 1
src/views/map/maplist/components/RightPanel.vue

@@ -4,7 +4,7 @@
     <div class="panel-header">
       <div class="panel-title">
         <h3>实时标定信息</h3>
-        <span class="map-name">(当前地图: {{ currentMap.name }})</span>
+        <span class="map-name">(当前地图: {{ mapName }})</span>
       </div>
       <el-button 
         @click="toggleCollapse" 
@@ -148,6 +148,10 @@
 export default {
   name: 'RightPanel',
   props: {
+    mapName: {
+      type: String,
+      required: true
+    },
     laserPositionData: {
       type: Object,
       default: () => ({ x: 0, y: 0, angle: 0 })

+ 64 - 5
src/views/map/maplist/navigation.vue

@@ -144,7 +144,7 @@
 						<el-tag type="danger" effect="dark" size="mini" v-if="nowHandMenu">{{ nowHandMenu }}</el-tag>
 					</div>
 					<OlMap ref="olmap" :width="olWidth + 'px'" :height="olHeight + 'px'" backgroundColor="#F5F5F5" :mapName="mapName"
-						:pointSwitch="settingParams.pointId" :baseLayerShow="settingParams.baseMap"
+						:pointSwitch="settingParams.pointId" :baseLayerShow="settingParams.baseMap" :robotPoseData="laserPositionData"
 						:pointSelectionEnabled="pointSelectionEnabled" :poseInitEnable="poseInitEnable" @addNowPoint="addNowPoint"
 						:isRobotFollow="settingParams.follow" @initNavigationResult="initNavigationResult"></OlMap>
 				</div>
@@ -256,19 +256,23 @@
 		<div class="fixed-right-center" @click="showInfoDra" v-if="!infoDrawer">
 			<i class="el-icon-arrow-left"></i>
 		</div>
+		<MqttComp ref="mqtt" :topics="topics" @message-received="onMessage" />
 	</div>
 </template>
 
 <script>
 import OlMap from "@/components/OlMap";
-
+import MqttComp from "@/components/Mqtt/mqttComp.vue";
 export default {
 	name: "Navigation",
 	components: {
-		OlMap
+		OlMap,
+		MqttComp
 	},
 	data() {
 		return {
+			topics:[this.$mqttPrefix+'/localization/action/init/reply',
+							this.$mqttPrefix + '/localization/pose'],
 			activeIndex: -1, // 默认为没有激活的项
 			settingDrawer: false,
 			pointDrawer: false,
@@ -330,14 +334,20 @@ export default {
 			olWidth: 0,  // 用于存储宽度的变量
 			olHeight: 0,
 			nowHandMenu: '',
-			mapName: this.$route.params.mapName || ''  // 地图名称
+			mapName: this.$route.params.mapName || '',  // 地图名称
+			// 激光定位数据
+			laserPositionData: {
+        x: 0,
+        y: 0,
+        angle: 0
+      },
 		};
 	},
 	created() {
 
 	},
 	mounted() {
-		const mapId = this.$route.params.mapId;	
+		// const mapId = this.$route.params.mapId;	
 		this.updateOlCss();
 		window.addEventListener('resize', this.updateOlCss);
 	},
@@ -345,6 +355,41 @@ export default {
 		window.removeEventListener('resize', this.updateOlCss);
 	},
 	methods: {
+		onMessage({ topic, message }) {
+        // console.log("收到消息:", topic, message);
+				if (topic === this.$mqttPrefix + '/localization/action/init/reply') {
+					this.handleInitReply(message);
+				} else if (topic === this.$mqttPrefix + '/localization/pose') {
+					this.handleLaserPose(message);
+				}
+    },
+		handleInitReply(message) {
+			// 处理初始化回复消息
+			console.log("初始化回复:", message);
+			
+		},
+		handleLaserPose(message) {
+			try {
+        const data = message.args[0];
+        const {xyz, rpy, blh, heading} = data.pose;
+        // 激光定位实时数据
+        this.laserPositionData.x = xyz[0];
+        this.laserPositionData.y = xyz[1];
+        this.laserPositionData.angle = rpy[2];
+        // GNSS定位实时数据
+        /* this.gnssPositionData.longitude = blh[1]; // 经度
+        this.gnssPositionData.latitude = blh[0]; // 纬度
+        this.gnssPositionData.angle = heading; // 航向角
+        
+        this.gnssPositionData.status = data.rtk.star+ '/' + data.rtk.status; // RTK状态 */
+        
+      } catch (e) {
+        console.error("解析失败:", e);
+      }
+		},
+		publishMsg() {
+        
+    },
 		updateOlCss() {
 			const element = this.$el.querySelector('.main-content');
 			this.olWidth = element.offsetWidth;
@@ -655,6 +700,20 @@ export default {
 		 * @param angle 角度
 		 */
 		initNavigationResult(position, angle) {
+			const prefix = process.env.VUE_APP_PNS_MQTT_PROXY;
+			console.log("prefix:"+prefix);
+			
+			this.$refs.mqtt.publish(prefix + "/localization/action/init", {
+    "timestamp" : 123456,
+    "args"      : [
+        {
+
+		"nid"  : 7
+           
+        }
+    ]
+}
+		);
 			console.log(position);
 			console.log(angle);
 		}