Map.vue 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987
  1. <template>
  2. <view
  3. id="mapContainer"
  4. :deviceList="deviceList"
  5. :landList="landList"
  6. :blockList="blockList"
  7. :deviceTypeMapData="deviceTypeMapData"
  8. :projectLocation="projectLocation"
  9. :mapRenderTypes="mapRenderTypes"
  10. :isShowSatellite="isShowSatellite"
  11. :inputValue="inputValue"
  12. :inputType="inputType"
  13. :deviceStatusCheckedList="deviceStatusCheckedList"
  14. :change:projectLocation="tianditu.onChange"
  15. :change:deviceList="tianditu.onChange"
  16. :change:landList="tianditu.onChange"
  17. :change:blockList="tianditu.onChange"
  18. :change:deviceTypeMapData="tianditu.onChange"
  19. :change:mapRenderTypes="tianditu.onChange"
  20. :change:isShowSatellite="tianditu.onChange"
  21. :change:deviceStatusCheckedList="tianditu.onChange"
  22. :change:inputValue="tianditu.searchChange"
  23. :change:inputType="tianditu.searchChange"
  24. :change:inputTag="tianditu.searchChange"
  25. class="map-container"
  26. ></view>
  27. </template>
  28. <script>
  29. import { normalize } from '@/util/helpers.js';
  30. // 地块数据示例(全部为[lng, lat]顺序,符合真实地理数据标准)
  31. const plots = [
  32. {
  33. blockId: '001',
  34. name: '001地块',
  35. type: '早田',
  36. area: 117.9,
  37. coords: [
  38. [117.123, 39.123],
  39. [117.125, 39.124],
  40. [117.124, 39.125],
  41. [117.123, 39.123]
  42. ],
  43. crops: [
  44. 'https://yunfei-agm.oss-cn-hangzhou.aliyuncs.com/tenant/YF10001/a34f7574-c675-45de-95d8-65fa9283170f/agmp/crop/20250625/微信截图_20230914151200_3cd33701018b1000e0019450c0a801dd_f5542b08844842a19494821115ad3284.jpg'
  45. ] // 示例:多种作物
  46. },
  47. {
  48. blockId: '002',
  49. name: '002地块',
  50. type: '早田',
  51. area: 56.96,
  52. coords: [
  53. [117.128, 39.126],
  54. [117.129, 39.127],
  55. [117.127, 39.128],
  56. [117.128, 39.126]
  57. ],
  58. crops: [
  59. 'https://yunfei-agm.oss-cn-hangzhou.aliyuncs.com/tenant/YF10001/a34f7574-c675-45de-95d8-65fa9283170f/agmp/crop/20250625/微信截图_20230914151200_3cd33701018b1000e0019450c0a801dd_f5542b08844842a19494821115ad3284.jpg'
  60. ] // 示例:单种作物
  61. },
  62. {
  63. blockId: '003',
  64. name: '003地块',
  65. type: '早田',
  66. area: 117.9,
  67. coords: [
  68. [113.905846, 33.937994],
  69. [113.909481, 33.938236],
  70. [113.909674, 33.936224],
  71. [113.905888, 33.935903]
  72. ],
  73. crops: [
  74. 'https://yunfei-agm.oss-cn-hangzhou.aliyuncs.com/tenant/YF10001/a34f7574-c675-45de-95d8-65fa9283170f/agmp/crop/20250625/微信截图_20230914151200_3cd33701018b1000e0019450c0a801dd_f5542b08844842a19494821115ad3284.jpg'
  75. ]
  76. },
  77. {
  78. blockId: '004',
  79. name: '004地块',
  80. type: '早田',
  81. area: 117.9,
  82. coords: [
  83. [113.905846, 33.937994, 0],
  84. [113.909481, 33.938236, 0],
  85. [113.909674, 33.936224, 0],
  86. [113.905888, 33.935903, 0]
  87. ],
  88. crops: [
  89. 'https://yunfei-agm.oss-cn-hangzhou.aliyuncs.com/tenant/YF10001/a34f7574-c675-45de-95d8-65fa9283170f/agmp/crop/20250625/微信截图_20230914151200_3cd33701018b1000e0019450c0a801dd_f5542b08844842a19494821115ad3284.jpg'
  90. ]
  91. },
  92. {
  93. blockId: '005',
  94. name: '005地块',
  95. type: '早田',
  96. area: 117.9,
  97. coords: [
  98. [113.9033, 33.937873, 0],
  99. [113.905815, 33.937989, 0],
  100. [113.905839, 33.935906, 0],
  101. [113.904849, 33.935828, 0]
  102. ],
  103. crops: [
  104. 'https://yunfei-agm.oss-cn-hangzhou.aliyuncs.com/tenant/YF10001/a34f7574-c675-45de-95d8-65fa9283170f/agmp/crop/20250625/微信截图_20230914151200_3cd33701018b1000e0019450c0a801dd_f5542b08844842a19494821115ad3284.jpg'
  105. ]
  106. },
  107. {
  108. blockId: '006',
  109. name: '006地块',
  110. type: '早田',
  111. area: 117.9,
  112. coords: [
  113. [113.905619, 33.94116, 0],
  114. [113.909302, 33.941068, 0],
  115. [113.909465, 33.93828, 0],
  116. [113.905848, 33.938069, 0]
  117. ],
  118. crops: [
  119. 'https://yunfei-agm.oss-cn-hangzhou.aliyuncs.com/tenant/YF10001/a34f7574-c675-45de-95d8-65fa9283170f/agmp/crop/20250625/微信截图_20230914151200_3cd33701018b1000e0019450c0a801dd_f5542b08844842a19494821115ad3284.jpg'
  120. ]
  121. }
  122. // 可继续添加更多地块
  123. ];
  124. export default {
  125. name: 'Map',
  126. props: {
  127. deviceList: {
  128. type: Array,
  129. default: () => []
  130. },
  131. landList: {
  132. type: Array,
  133. default: () => []
  134. },
  135. blockList: {
  136. type: Array,
  137. default: () => []
  138. },
  139. mapRenderTypes: {
  140. type: Array,
  141. default: () => []
  142. },
  143. deviceStatusCheckedList: {
  144. type: Array,
  145. default: () => ['0', '1', '3']
  146. },
  147. isShowSatellite: {
  148. type: Boolean,
  149. default: true
  150. },
  151. inputValue: {
  152. type: String,
  153. default: ''
  154. },
  155. inputType: {
  156. type: String,
  157. default: ''
  158. },
  159. inputTag: {
  160. type: Boolean,
  161. default: false
  162. }
  163. },
  164. data() {
  165. return {
  166. dataPending: false,
  167. deviceTypeList: [],
  168. projectLocation: {}
  169. // blockList: plots
  170. };
  171. },
  172. computed: {
  173. deviceTypeMapData() {
  174. return normalize(this.deviceTypeList, 'devtypeBid');
  175. }
  176. },
  177. watch: {
  178. deviceList: {
  179. handler(newVal) {
  180. if (newVal.length > 0) {
  181. if (this.myMap) {
  182. this.dataPending = false;
  183. this.initDeviceMarkers();
  184. } else {
  185. this.dataPending = true;
  186. }
  187. }
  188. },
  189. deep: true
  190. },
  191. landList(newVal) {
  192. if (newVal.length > 0) {
  193. if (this.myMap) {
  194. this.dataPending = false;
  195. this.initLandMarkers();
  196. } else {
  197. this.dataPending = true;
  198. }
  199. }
  200. }
  201. },
  202. mounted() {
  203. this.getDeviceTypeList();
  204. this.getProjectLocation();
  205. },
  206. methods: {
  207. handleMarkerClick(item) {
  208. this.$emit('deviceClick', item);
  209. },
  210. handleLandMarkerClick(item) {
  211. this.$emit('landClick', item);
  212. },
  213. handlePlotClick(item) {
  214. this.$emit('plotClick', item);
  215. },
  216. getDeviceTypeList() {
  217. },
  218. getProjectLocation() {
  219. },
  220. showToast() {
  221. uni.showToast({
  222. title: '未查询到数据',
  223. icon: 'none'
  224. });
  225. }
  226. }
  227. };
  228. </script>
  229. <script module="tianditu" lang="renderjs">
  230. import { convertToGrayscale } from '@/util/helpers';
  231. import {debounce} from 'lodash-es'
  232. let lay = null;
  233. let cluster = null;
  234. let originalMarkers = [];
  235. let timer = null;
  236. let currentDeviceMarkers = [];
  237. let currentDeviceLabelMarkers = [];
  238. let currentLandMarkers = [];
  239. let currentLandLabelMarkers = [];
  240. let currentPlotPolygons = [];
  241. let currentPlotCropMarkers = [];
  242. let currentPlotLabels = [];
  243. let deviceMarkerIndexMap = {};
  244. let landMarkerIndexMap = {};
  245. let plotMarkerIndexMap = {};
  246. let isShowLabel = false;
  247. let currentZoom = 5;
  248. export default {
  249. mounted() {
  250. window.wx = undefined
  251. if (typeof window.T === 'function') {
  252. this.initMap()
  253. } else {
  254. // 动态引入较大类库避免影响页面展示
  255. const script = document.createElement('script')
  256. // view 层的页面运行在 www 根目录,其相对路径相对于 www 计算
  257. script.src = 'https://api.tianditu.gov.cn/api?v=4.0&tk=bfe366f1af60602bd0e51853af6d23d2'
  258. script.onload = this.initMap.bind(this)
  259. document.head.appendChild(script)
  260. }
  261. },
  262. methods: {
  263. initMap() {
  264. console.log('initMap', this.deviceList, this.landList,this.dataPending);
  265. var imageURL =
  266. 'https://t0.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=bfe366f1af60602bd0e51853af6d23d2';
  267. //创建自定义图层对象
  268. lay = new T.TileLayer(imageURL, {
  269. minZoom: 2,
  270. maxZoom: 18
  271. });
  272. this.myMap = new T.Map('mapContainer', {
  273. // layers: [lay],
  274. });
  275. // 监听地图事件
  276. this.myMap.addEventListener('zoomend', this.zoomend);
  277. // this.myMap.addEventListener('moveend', this.updateLabels);
  278. const {lng, lat} = this.projectLocation || {};
  279. console.log(lng,lat)
  280. this.myMap.centerAndZoom(new T.LngLat(lng || 116.40769, lat || 39.89945), 12);
  281. // this.myMap.setMaxBounds(new T.LngLatBounds(new T.LngLat(116.10249, 39.84677), new T.LngLat(116.71291, 39.95212)));
  282. // indigo black default
  283. this.myMap.setStyle('default');
  284. if(this.dataPending) {
  285. this.initDeviceMarkers();
  286. this.initLandMarkers();
  287. this.dataPending = false;
  288. }
  289. // 初始化地块polygon
  290. this.initPlotPolygons();
  291. this.onChange();
  292. },
  293. searchChange(){
  294. currentDeviceLabelMarkers.forEach(marker => {
  295. const labelDom = marker.getElement();
  296. labelDom.classList.remove('active');
  297. });
  298. currentLandLabelMarkers.forEach(marker => {
  299. const labelDom = marker.getElement();
  300. labelDom.classList.remove('active');
  301. });
  302. currentPlotLabels.forEach(marker => {
  303. const labelDom = marker.getElement();
  304. labelDom.classList.remove('active');
  305. });
  306. if(this.inputType){
  307. if(this.inputType === 'device'){
  308. if(this.inputValue){
  309. const deviceData = this.deviceList && this.deviceList.find(item=>{
  310. const lng = item.devLngalign || item.devLng;
  311. const lat = item.devLatalign || item.devLat;
  312. return (item.devCode.includes(this.inputValue) || item.devName.includes(this.inputValue)) && lng && lat
  313. })
  314. if(deviceData){
  315. const deviceMarker = currentDeviceMarkers[deviceMarkerIndexMap[deviceData.devBid]];
  316. const labelMarker = currentDeviceLabelMarkers[deviceMarkerIndexMap[deviceData.devBid]];
  317. console.log(deviceData.devBid,deviceMarkerIndexMap,'deviceMarkerdeviceMarkerdeviceMarkerdeviceMarkerdeviceMarker')
  318. const lnglat = deviceMarker?.getLngLat()
  319. labelMarker && labelMarker.show()
  320. currentDeviceLabelMarkers && currentDeviceLabelMarkers.forEach(marker => {
  321. const labelDom = marker.getElement();
  322. labelDom.classList.remove('active');
  323. });
  324. if(labelMarker){
  325. const currentlabelDom = labelMarker && labelMarker.getElement();
  326. console.log(labelMarker,'-----------------2342342342---- label dom',currentlabelDom)
  327. currentlabelDom.classList.add('active');
  328. }
  329. if(lnglat){
  330. this.myMap && this.myMap.centerAndZoom(lnglat,18)
  331. }
  332. }else{
  333. this.$ownerInstance.callMethod('showToast')
  334. }
  335. }
  336. }else if(this.inputType === 'land'){
  337. if(this.inputValue){
  338. const landData = this.landList.find(item=>{
  339. return item.landId.includes(this.inputValue) || item.landName.includes(this.inputValue)
  340. })
  341. if(landData){
  342. const landMarker = currentLandMarkers[landMarkerIndexMap[landData.landId]];
  343. const lnglat = landMarker?.getLngLat();
  344. const labelMarker = currentLandLabelMarkers[landMarkerIndexMap[landData.landId]];
  345. labelMarker && labelMarker.show()
  346. currentLandLabelMarkers.forEach(marker => {
  347. const labelDom = marker.getElement();
  348. console.log(marker,'--------------------- label dom',labelDom)
  349. labelDom.classList.remove('active');
  350. });
  351. if(labelMarker){
  352. const currentlabelDom = labelMarker.getElement();
  353. console.log(labelMarker,'--------------------- label dom',currentlabelDom)
  354. currentlabelDom.classList.add('active');
  355. }
  356. if(lnglat){
  357. this.myMap && this.myMap.centerAndZoom(lnglat,18)
  358. }
  359. }else{
  360. this.$ownerInstance.callMethod('showToast')
  361. }
  362. }
  363. }else if(this.inputType === 'block' || this.inputType === 'crop'){
  364. if(this.inputValue){
  365. // 查找所有匹配的地块数据
  366. const matchedPlots = this.blockList && this.blockList.filter(item=>{
  367. const cropData = item.fmsPlanCropResVoList && item.fmsPlanCropResVoList.find(crop=>{
  368. return crop.cropName.includes(this.inputValue)
  369. })
  370. return item.blockName.includes(this.inputValue) || cropData
  371. })
  372. if(matchedPlots && matchedPlots.length > 0){
  373. // 清除所有标签的高亮状态
  374. currentPlotLabels.forEach(marker => {
  375. const labelDom = marker.getElement();
  376. labelDom.classList.remove('active');
  377. });
  378. const allCoordinates = [];
  379. const matchedLabels = [];
  380. // 处理所有匹配的地块
  381. matchedPlots.forEach(plotData => {
  382. const plotIndex = plotMarkerIndexMap[plotData.blockId];
  383. const plotPolygon = currentPlotPolygons[plotIndex];
  384. const plotLabel = currentPlotLabels[plotIndex];
  385. if(plotPolygon && plotLabel){
  386. const blockLngrange =
  387. typeof plotData.blockLngrange === 'string' && plotData.blockLngrange ? JSON.parse(plotData.blockLngrange) : plotData.blockLngrange || {};
  388. const coordinates = blockLngrange?.geometry?.coordinates || [];
  389. const coords = coordinates[0] || []
  390. // 收集所有坐标点用于计算视窗
  391. coords.forEach(coord => {
  392. allCoordinates.push(new T.LngLat(coord[0], coord[1]));
  393. });
  394. // 收集匹配的标签
  395. matchedLabels.push(plotLabel);
  396. }
  397. });
  398. // 高亮显示所有匹配的标签
  399. if(this.mapRenderTypes.includes('block')){
  400. matchedLabels.forEach(label => {
  401. label.show();
  402. const labelDom = label.getElement();
  403. labelDom.classList.add('active');
  404. });
  405. }
  406. // 计算包含所有匹配地块的视窗
  407. if(allCoordinates.length > 0){
  408. if(allCoordinates.length === 1){
  409. // 只有一个点,直接居中缩放
  410. this.myMap && this.myMap.centerAndZoom(allCoordinates[0], 16);
  411. } else {
  412. // 多个点,设置视窗包含所有点
  413. this.myMap && this.myMap.setViewport(allCoordinates);
  414. }
  415. }
  416. }else{
  417. this.$ownerInstance.callMethod('showToast')
  418. }
  419. }
  420. }
  421. }
  422. },
  423. zoomend(){
  424. console.log(this.myMap.getZoom(),'----------------------- zoom end')
  425. currentZoom = this.myMap.getZoom();
  426. isShowLabel = currentZoom >= 15;
  427. this.onChange();
  428. },
  429. onChange:debounce(function (){
  430. console.log('onChange',isShowLabel, this.mapRenderTypes);
  431. const mapRenderTypes = this.mapRenderTypes || ['device','land','plot']
  432. const deviceStatusList = this.deviceStatusCheckedList || ['0','1', '3']
  433. clearTimeout(timer);
  434. timer = setTimeout(() => {
  435. if(!this.myMap){
  436. return;
  437. }
  438. if(mapRenderTypes.includes('device')){
  439. if(currentDeviceMarkers && currentDeviceMarkers.length > 0){
  440. currentDeviceMarkers.forEach(marker => {
  441. console.log(marker.customData,deviceStatusList)
  442. const isShow = deviceStatusList.includes(marker.customData.status);
  443. if(isShow){
  444. marker.show()
  445. }else{
  446. marker.hide()
  447. }
  448. });
  449. currentDeviceLabelMarkers.forEach(marker => {
  450. const isShow = deviceStatusList.includes(marker.customData.status);
  451. if(isShowLabel && isShow){
  452. marker.show()
  453. }else{
  454. marker.hide()
  455. }
  456. });
  457. }else{
  458. this.initDeviceMarkers();
  459. }
  460. }else{
  461. if(currentDeviceMarkers && currentDeviceMarkers.length > 0){
  462. currentDeviceMarkers.forEach(marker => {
  463. marker.hide()
  464. });
  465. currentDeviceLabelMarkers.forEach(marker => {
  466. marker.hide()
  467. });
  468. }
  469. }
  470. if(mapRenderTypes.includes('land')){
  471. if(currentLandMarkers && currentLandMarkers.length > 0){
  472. currentLandMarkers.forEach(marker => {
  473. marker.show()
  474. });
  475. currentLandLabelMarkers.forEach(marker => {
  476. if(isShowLabel){
  477. marker.show()
  478. }else{
  479. marker.hide()
  480. }
  481. });
  482. }else{
  483. this.initLandMarkers();
  484. }
  485. }else{
  486. if(currentLandMarkers && currentLandMarkers.length > 0){
  487. currentLandMarkers.forEach(marker => {
  488. marker.hide()
  489. });
  490. currentLandLabelMarkers.forEach(marker => {
  491. marker.hide()
  492. });
  493. }
  494. }
  495. // 处理地块polygon显示
  496. if(mapRenderTypes.includes('block') || mapRenderTypes.includes('crop')){
  497. const isShowBlock = mapRenderTypes.includes('block');
  498. const isShowCrop = mapRenderTypes.includes('crop');
  499. if(currentPlotPolygons && currentPlotPolygons.length > 0){
  500. currentPlotPolygons.forEach(polygon => {
  501. if(!isShowBlock){
  502. polygon.hide()
  503. }else{
  504. polygon.show()
  505. }
  506. });
  507. currentPlotCropMarkers.forEach(marker => {
  508. if(!isShowCrop){
  509. marker.hide()
  510. }else{
  511. marker.show()
  512. }
  513. });
  514. currentPlotLabels.forEach(label => {
  515. if(!isShowBlock){
  516. label.hide()
  517. }else {
  518. label.show()
  519. }
  520. });
  521. }else{
  522. this.initPlotPolygons();
  523. }
  524. }else{
  525. if(currentPlotPolygons && currentPlotPolygons.length > 0){
  526. currentPlotPolygons.forEach(polygon => {
  527. polygon.hide()
  528. });
  529. currentPlotCropMarkers.forEach(marker => {
  530. marker.hide()
  531. });
  532. currentPlotLabels.forEach(label => {
  533. label.hide()
  534. });
  535. }
  536. }
  537. this.toggleTileLayer(this.isShowSatellite);
  538. }, 500);
  539. },500),
  540. initDeviceMarkers() {
  541. console.log('init device markers ',this.deviceList)
  542. if(!this.deviceList){
  543. return
  544. }
  545. // 设备标记
  546. const markers = []
  547. const bounds = new T.LngLatBounds();
  548. const lnglatList = [];
  549. const labels = [];
  550. let index = 0;
  551. const len = this.deviceList && this.deviceList.length || 0;
  552. this.deviceList.forEach((item) => {
  553. const lng = item.devLngalign || item.devLng;
  554. const lat = item.devLatalign || item.devLat;
  555. if (!lng || !lat) {
  556. return;
  557. }
  558. let img = this.deviceTypeMapData[item.devtypeBid] ? this.deviceTypeMapData[item.devtypeBid].devtypeMapicon : '';
  559. // img 转base64图片地址
  560. var icon = new T.Icon({
  561. iconUrl: img || 'http://api.tianditu.gov.cn/v4.0/image/marker-icon.png',
  562. iconSize: new T.Point(25, 35),
  563. iconAnchor: new T.Point(0, 25)
  564. });
  565. const lngLat = new T.LngLat(lng, lat);
  566. const marker = new T.Marker(lngLat,{icon: icon,className: item.devStatus == '1' ?'tdt-online' : 'tdt-offline' });
  567. markers.push(marker);
  568. bounds.extend(lngLat);
  569. lnglatList.push(lngLat);
  570. const label = new T.Label({
  571. text: `<div class="device-label">${item.devName}</div>`,
  572. position: marker.getLngLat(),
  573. offset: new T.Point(0, -40)
  574. });
  575. marker.addEventListener("click", () => {
  576. this.$ownerInstance.callMethod('handleMarkerClick',item)
  577. });
  578. label.addEventListener("click",()=>{
  579. this.$ownerInstance.callMethod('handleMarkerClick',item)
  580. });
  581. labels.push(label);
  582. deviceMarkerIndexMap[item.devBid] = index;
  583. index++;
  584. marker.customData = {
  585. id:item.devBid,
  586. status:item.devStatus
  587. }
  588. label.customData = {
  589. id:item.devBid,
  590. status:item.devStatus
  591. }
  592. this.myMap && this.myMap.addOverLay(label);
  593. this.myMap && this.myMap.addOverLay(marker);
  594. if(!isShowLabel && len > 1){
  595. label.hide();
  596. }
  597. const iconDom = marker.getIcon().img;
  598. iconDom.classList.remove('tdt-online', 'tdt-offline');
  599. iconDom.classList.add(`${item.devStatus === '1' ? 'tdt-online' : 'tdt-offline' }`);
  600. });
  601. originalMarkers=[...markers];
  602. currentDeviceMarkers=[...markers];
  603. currentDeviceLabelMarkers=[...labels];
  604. if(this.myMap){
  605. // cluster = new T.MarkerClusterer(this.myMap, {markers});
  606. setTimeout(() => {
  607. this.myMap && this.myMap.setViewport(lnglatList);
  608. }, 1000);
  609. }
  610. },
  611. initLandMarkers() {
  612. console.log('initLandMarkers', this.landList);
  613. if(!this.landList){
  614. return
  615. }
  616. // 设备标记
  617. const markers = [];
  618. const labels = [];
  619. let index = 0;
  620. this.landList.forEach((item) => {
  621. const lng = item.landLongitude || '';
  622. const lat = item.landLatitude || '';
  623. if (!lng || !lat) {
  624. return;
  625. }
  626. //创建图片对象
  627. var icon = new T.Icon({
  628. iconUrl: "./static/images/home/base.png",
  629. iconSize: new T.Point(70, 60),
  630. iconAnchor: new T.Point(10, 25)
  631. });
  632. const marker = new T.Marker(new T.LngLat(lng, lat), {icon: icon});
  633. markers.push(marker);
  634. this.myMap && this.myMap.addOverLay(marker);
  635. const label = new T.Label({
  636. text: `<div class="land-label">${item.landName}</div>`,
  637. position: marker.getLngLat(),
  638. offset: new T.Point(0, -40)
  639. });
  640. labels.push(label);
  641. this.myMap && this.myMap.addOverLay(label);
  642. landMarkerIndexMap[item.landId] = index;
  643. index++;
  644. marker.addEventListener("click", () => {
  645. this.$ownerInstance.callMethod('handleLandMarkerClick',item)
  646. });
  647. label.addEventListener("click",()=>{
  648. this.$ownerInstance.callMethod('handleLandMarkerClick',item)
  649. });
  650. if(!isShowLabel){
  651. label.hide()
  652. }
  653. });
  654. currentLandMarkers=[...markers];
  655. currentLandLabelMarkers=[...labels];
  656. },
  657. initPlotPolygons() {
  658. console.log('initPlotPolygons', this.blockList);
  659. if(!this.blockList){
  660. return
  661. }
  662. const polygons = [];
  663. const cropMarkers = [];
  664. const labels = [];
  665. let index = 0;
  666. this.blockList.forEach((plot,index) => {
  667. const blockLngrange =
  668. typeof plot.blockLngrange === 'string' && plot.blockLngrange ? JSON.parse(plot.blockLngrange) : plot.blockLngrange || {};
  669. const coordinates = blockLngrange?.geometry?.coordinates || [];
  670. if (!coordinates[0] || coordinates[0].length < 3) {
  671. return;
  672. }
  673. // 创建地块polygon的坐标点数组
  674. const points = coordinates[0].map(coord => {
  675. // 处理可能包含高度信息的坐标 [lng, lat, height] 或 [lng, lat]
  676. const lng = coord[0];
  677. const lat = coord[1];
  678. return new T.LngLat(lng, lat);
  679. });
  680. // 创建polygon
  681. const polygon = new T.Polygon(points, {
  682. color: plot.blockColor || '#1890ff',
  683. weight: 2,
  684. opacity: 1,
  685. fillColor: plot.blockColor || '#1890ff',
  686. fillOpacity: 0.7
  687. });
  688. polygons.push(polygon);
  689. this.myMap && this.myMap.addOverLay(polygon);
  690. // 计算地块中心点用于放置作物图片
  691. const center = this.calculatePolygonCenter(points);
  692. const plotLabel = new T.Label({
  693. text: `<div class="plot-label">${plot.blockName}</div>`,
  694. position: center,
  695. offset: new T.Point(0, 10)
  696. });
  697. labels.push(plotLabel);
  698. this.myMap && this.myMap.addOverLay(plotLabel);
  699. // 添加点击事件
  700. polygon.addEventListener("click", () => {
  701. this.$ownerInstance.callMethod('handlePlotClick', plot);
  702. });
  703. plotLabel.addEventListener("click", () => {
  704. this.$ownerInstance.callMethod('handlePlotClick', plot);
  705. });
  706. // 为每个作物创建标记
  707. if (plot.fmsPlanCropResVoList && plot.fmsPlanCropResVoList.length > 0) {
  708. plot.fmsPlanCropResVoList.forEach((cropItem, cropIndex) => {
  709. const cropUrl = cropItem.cropPreview || ''
  710. if(cropUrl){
  711. // 如果有多个作物,稍微偏移位置
  712. const offsetX = cropIndex * 30 - (plot.fmsPlanCropResVoList.length - 1) * 15;
  713. const offsetY = 0;
  714. const cropIcon = new T.Icon({
  715. iconUrl: cropUrl,
  716. iconSize: new T.Point(20, 20),
  717. iconAnchor: new T.Point(20, 20),
  718. className: 'crop-icon-rounded'
  719. });
  720. const cropMarker = new T.Marker(center, {icon: cropIcon});
  721. cropMarkers.push(cropMarker);
  722. this.myMap && this.myMap.addOverLay(cropMarker);
  723. cropMarker.addEventListener("click", () => {
  724. this.$ownerInstance.callMethod('handlePlotClick', plot);
  725. });
  726. }
  727. });
  728. }
  729. plotMarkerIndexMap[plot.blockId] = index;
  730. index++;
  731. });
  732. currentPlotPolygons = [...polygons];
  733. currentPlotCropMarkers = [...cropMarkers];
  734. currentPlotLabels = [...labels];
  735. },
  736. calculatePolygonCenter(points) {
  737. // 计算polygon的中心点
  738. let totalLng = 0;
  739. let totalLat = 0;
  740. points.forEach(point => {
  741. totalLng += point.lng;
  742. totalLat += point.lat;
  743. });
  744. return new T.LngLat(totalLng / points.length, totalLat / points.length);
  745. },
  746. toggleTileLayer(isShowSatelliteLayer=true) {
  747. console.log('toggleTileLayer',isShowSatelliteLayer);
  748. if (!this.myMap) {
  749. return
  750. }
  751. if(isShowSatelliteLayer){
  752. this.myMap.addLayer(lay);
  753. }else{
  754. this.myMap.removeLayer(lay);
  755. }
  756. },
  757. // 更新Label可见性
  758. updateLabels() {
  759. setTimeout(() => {
  760. console.log('updateLabels',cluster);
  761. console.log('updateLabels origin markers',originalMarkers);
  762. // console.log('updateLabels origin markers',cluster && cluster.getClustersCount());
  763. }, 1000);
  764. // cluster.getMarkers().forEach(marker => {
  765. // marker.getLabel().hide();
  766. // });
  767. // originalMarkers.forEach(marker => {
  768. // if (!this.isMarkerInAnyCluster(marker)) {
  769. // marker.getLabel().show();
  770. // }
  771. // });
  772. },
  773. isMarkerInAnyCluster(marker) {
  774. // return cluster.getMarkers().some(markers =>{
  775. // }
  776. // cluster.markers.includes(marker)
  777. // );
  778. }
  779. }
  780. }
  781. </script>
  782. <style lang="scss" scoped>
  783. .map-container {
  784. width: 100%;
  785. height: 100vh;
  786. }
  787. ::v-deep .tdt-label {
  788. background: transparent;
  789. border-color: transparent;
  790. border-radius: 999px;
  791. min-width: 30px;
  792. padding: 0;
  793. line-height: 20px;
  794. text-align: center;
  795. box-shadow: none;
  796. .land-label {
  797. background: rgba(0, 0, 0, 0.5);
  798. border-color: rgba(255, 199, 0, 0.18);
  799. border-radius: 999px;
  800. padding: 0 10px;
  801. color: #ffc701;
  802. min-width: 30px;
  803. line-height: 20px;
  804. transform: translateX(-50%);
  805. }
  806. .device-label {
  807. background: rgba(0, 0, 0, 0.5);
  808. border: 1px solid #1890ff;
  809. border-radius: 4px;
  810. color: #fff;
  811. min-width: 30px;
  812. padding: 0 10px;
  813. line-height: 20px;
  814. transform: translateX(-50%);
  815. }
  816. .plot-label {
  817. border: none;
  818. border-radius: 4px;
  819. color: #fff;
  820. min-width: 40px;
  821. padding: 2px 8px;
  822. line-height: 18px;
  823. transform: translateX(-50%);
  824. text-align: center;
  825. font-size: 14px;
  826. font-weight: normal;
  827. }
  828. &.active {
  829. .device-label,
  830. .land-label {
  831. border: 2px solid gold;
  832. box-shadow: 0 0 10px rgba(255, 215, 0, 0.6),
  833. /* 金色阴影 */ 0 0 20px rgba(255, 215, 0, 0.4),
  834. /* 较淡的金色阴影 */ 0 0 30px rgba(255, 215, 0, 0.2);
  835. }
  836. .plot-label {
  837. background: rgba(255, 255, 255, 1);
  838. border: 1px solid #1890ff;
  839. color: #1890ff;
  840. font-weight: 500;
  841. }
  842. }
  843. }
  844. ::v-deep .tdt-marker-icon {
  845. /* 在线状态 */
  846. &.tdt-online {
  847. filter: grayscale(0%) brightness(1.2);
  848. }
  849. /* 离线状态 */
  850. &.tdt-offline {
  851. filter: grayscale(100%) opacity(0.6);
  852. }
  853. }
  854. ::v-deep .tdt-bottom {
  855. display: none;
  856. }
  857. /* 作物图片圆角样式 */
  858. ::v-deep img.crop-icon-rounded {
  859. border-radius: 50%;
  860. border: 1px solid #fff;
  861. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  862. }
  863. </style>