mapDraw.js 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950
  1. // 闭包封装:内部所有变量和方法全部私有化!
  2. (function (window) {
  3. /**
  4. * MapDraw 独立封装类
  5. * 引入后直接 new MapDraw(mapboxMap实例) 即可使用
  6. */
  7. class MapDraw {
  8. /**
  9. * 构造函数:new MapDraw() 时自动执行
  10. * @param {mapboxgl.Map} map - 已经初始化好的 mapbox 实例
  11. */
  12. constructor(map, callbacks = {}) {
  13. const that = this;
  14. // 接收 map 实例
  15. that.map = map;
  16. // 编辑完成后 → 调用这个回调把数据抛出去
  17. that.onEditComplete = callbacks.onEditComplete || null;
  18. that.tempData = null; // 当前绘制的图形数据
  19. that.tempSource = null; // 当前绘制的source
  20. that.tempPoints = []; // 存储绘制的点
  21. that.drawType = ''; // 绘制类型
  22. that.isDrawing = false; // 是否正在绘制
  23. that.hoveredPolygonId = null; // 当前悬停的面ID
  24. that.tempLineSource = null; // 当前绘制的线的source
  25. that.itemId = ''; // 当前绘制的图形ID
  26. that.totalDistance = 0; // 总距离
  27. that.drawMarkers = []; // 存储节点标签
  28. that.moveMarkerTips = null; //移动节点显示提示标签
  29. // 用一个变量防止双击触发单击
  30. that.lastClickTime = 0;
  31. that.clickTimer = null;
  32. that.clickCount = 0;
  33. // 编辑相关状态
  34. that.mouseDownPos = null; // 鼠标按下时的位置
  35. that.isDragWhole = false; // 拖拽整个图形
  36. that.lastLngLat = null; // 最后一次点击的经纬度
  37. that.isEditMode = false; // 编辑模式
  38. that.isEditFlag = false; // 是否正在编辑
  39. that.selectedFeature = null; // 当前选中的图形
  40. that.selectedFeatureId = ''; // 当前选中图形的ID
  41. that.layersIds = []; // 当前选中图形的图层ID
  42. that.editMarkers = []; // 存储编辑节点的标记
  43. that.isDragging = false; // 是否正在拖拽
  44. that.draggingPointIndex = -1; // 正在拖拽的节点索引
  45. that.dragEventHandlers = null; // 拖拽事件处理器
  46. that.dragEventDrawHandlers = null; // 拖拽事件处理器2
  47. that.unit = 'kilometers'; // 单位 (kilometers/meters)
  48. that.unitLabel = 'km'; // 单位标签 (km/m)
  49. that.editLayerIds = []; // 当前选中图形的图层ID
  50. // 缓存事件句柄,用于精准解绑(核心防重复触发)
  51. that._mapClickHandle = null;
  52. that._mapDblClickHandle = null;
  53. that._mapMouseDownHandle = null;
  54. that._mapMouseMoveHandle = null;
  55. that._mapMouseUpHandle = null;
  56. // 加载地图绘制图层
  57. that.loadDrawLayer();
  58. // 绑定地图事件
  59. that.bindMapEvents();
  60. }
  61. // 编辑后回调返回的完整数据格式
  62. getCompleteFeature(feature) {
  63. const that = this;
  64. // 复制一个全新的对象,和原 feature 无关
  65. let feat = JSON.parse(JSON.stringify(feature));
  66. delete feat.properties.drawType;
  67. delete feat.properties.isCircle;
  68. if (that.onEditComplete) {
  69. that.onEditComplete({
  70. type: "Feature",
  71. geometry: feat.geometry,
  72. properties: feat.properties || {}
  73. });
  74. }
  75. }
  76. // 绑定地图事件
  77. bindMapEvents() {
  78. const that = this;
  79. // ========== 精准解绑所有旧鼠标事件,防止触发两次 ==========
  80. if(that._mapClickHandle) that.map.off('click', that._mapClickHandle);
  81. if(that._mapDblClickHandle) that.map.off('dblclick', that._mapDblClickHandle);
  82. if(that._mapMouseDownHandle) that.map.off('mousedown', that._mapMouseDownHandle);
  83. if(that._mapMouseMoveHandle) that.map.off('mousemove', that._mapMouseMoveHandle);
  84. if(that._mapMouseUpHandle) that.map.off('mouseup', that._mapMouseUpHandle);
  85. // 清空旧句柄
  86. that._mapClickHandle = null;
  87. that._mapDblClickHandle = null;
  88. that._mapMouseDownHandle = null;
  89. that._mapMouseMoveHandle = null;
  90. that._mapMouseUpHandle = null;
  91. // 初始化编辑鼠标事件
  92. that.initMapEditEvents();
  93. // 单击事件【缓存句柄】
  94. that._mapClickHandle = (e) => {
  95. that.handleFeatureClick(e);
  96. if (that.isEditMode) {
  97. const now = Date.now();
  98. const DOUBLE_CLICK_THRESHOLD = 300;
  99. clearTimeout(that.clickTimer);
  100. if (now - that.lastClickTime > DOUBLE_CLICK_THRESHOLD) {
  101. that.clickCount = 1;
  102. that.lastClickTime = now;
  103. that.clickTimer = setTimeout(() => {
  104. if (that.clickCount === 1) {
  105. that.handleMapClick(e);
  106. }
  107. that.clickCount = 0;
  108. }, 10);
  109. } else {
  110. that.clickCount = 2;
  111. that.lastClickTime = 0;
  112. }
  113. }
  114. };
  115. that.map.on('click', that._mapClickHandle);
  116. // 双击事件【缓存句柄】
  117. that._mapDblClickHandle = (e) => {
  118. clearTimeout(that.clickTimer);
  119. that.clickCount = 0;
  120. that.lastClickTime = 0;
  121. that.handleMapDblClick(e);
  122. };
  123. that.map.on('dblclick', that._mapDblClickHandle);
  124. // ========== 键盘 DEL 删除选中节点 ==========
  125. that.bindDeleteHotkey();
  126. }
  127. initMapEditEvents() {
  128. const that = this;
  129. // 鼠标按下【缓存句柄】
  130. that._mapMouseDownHandle = (evt) => {
  131. // console.log('[ _mapMouseDownHandle ] >', that.selectedFeature)
  132. if (!that.selectedFeature) return;
  133. if(that.selectedFeature.properties.drawType === 'distance' || that.selectedFeature.properties.drawType === 'area') return;
  134. // console.log('[ _mapMouseDownHandle ] >', evt)
  135. that.mouseDownPos = evt.point;
  136. that.lastLngLat = evt.lngLat;
  137. that.isDragging = false;
  138. evt.preventDefault();
  139. that.map.getCanvas().style.cursor = 'grabbing';
  140. };
  141. that.map.on('mousedown', that._mapMouseDownHandle);
  142. // 鼠标移动【缓存句柄】
  143. that._mapMouseMoveHandle = (evt) => {
  144. if (!that.selectedFeature) return;
  145. if ((that.isEditMode || that.isEditFlag) && that.isDragging) {
  146. that.map.getCanvas().style.cursor = 'move';
  147. that.handleDrag(evt);
  148. }
  149. // console.log('[ _mapMouseMoveHandle ] >', that.selectedFeature)
  150. if (!that.mouseDownPos || !that.selectedFeature) return;
  151. if(that.selectedFeature.properties.drawType === 'distance' || that.selectedFeature.properties.drawType === 'area') return;
  152. that.draggingPointIndex = -1;
  153. const dx = Math.abs(evt.point.x - that.mouseDownPos.x);
  154. const dy = Math.abs(evt.point.y - that.mouseDownPos.y);
  155. if (dx > 1 || dy > 1) {
  156. that.isDragging = true;
  157. const curLngLat = evt.lngLat;
  158. const dLng = curLngLat.lng - that.lastLngLat.lng;
  159. const dLat = curLngLat.lat - that.lastLngLat.lat;
  160. that.clearEditMarkers();
  161. that.moveWholeFeature(dLng, dLat);
  162. that.lastLngLat = curLngLat;
  163. }
  164. };
  165. that.map.on('mousemove', that._mapMouseMoveHandle);
  166. // 鼠标抬起【缓存句柄】
  167. that._mapMouseUpHandle = () => {
  168. if (!that.selectedFeature) return;
  169. that.mouseDownPos = null;
  170. if (that.isDragging) {
  171. that.getCompleteFeature(that.selectedFeature);
  172. if(that.selectedFeature.properties.drawType === 'distance' || that.selectedFeature.properties.drawType === 'area'){
  173. that.clearEditMarkers();
  174. }else{
  175. that.showEditMarkers();
  176. }
  177. }else{
  178. that.clearEditMarkers();
  179. }
  180. // console.log('[ _mapMouseUpHandle ] >', that.selectedFeature)
  181. that.isDragging = false;
  182. // that.draggingPointIndex = -1;
  183. that.tempLineSource.setData({ type: 'FeatureCollection', features: [] });
  184. that.map.getCanvas().style.cursor = '';
  185. // console.log('[ _mapMouseUpHandle==== ] >', that.selectedFeature)
  186. };
  187. that.map.on('mouseup', that._mapMouseUpHandle);
  188. }
  189. // 剩余原有方法完全不变
  190. loadDrawLayer(){
  191. const that = this;
  192. if(!that.map.getSource('temp-line')){
  193. that.map.addSource('temp-line', {
  194. type: 'geojson',
  195. data: { type: 'FeatureCollection', features: [] }
  196. });
  197. that.map.addLayer({
  198. id: 'temp-line-layer',
  199. type: 'line',
  200. source: 'temp-line',
  201. paint: {
  202. 'line-color': 'red',
  203. 'line-width': 2,
  204. 'line-dasharray': [3, 3]
  205. }
  206. });
  207. }
  208. that.tempLineSource = that.map.getSource('temp-line');
  209. that.map.on('mousemove', (e) => {
  210. if (!that.isDrawing) return
  211. if(that.moveMarkerTips){
  212. that.moveMarkerTips.setLngLat(e.lngLat);
  213. }else{
  214. let text = "鼠标双击完成";
  215. if(that.drawType === 'circle' || that.drawType === 'rectangle'){
  216. text = "鼠标双击/单击完成";
  217. }
  218. that.addMoveMarkerTips(e.lngLat, text);
  219. }
  220. const end = [e.lngLat.lng, e.lngLat.lat];
  221. if (that.drawType === 'rectangle'){
  222. if (that.tempPoints.length < 1) return;
  223. const bbox = [
  224. Math.min(that.tempPoints[0][0], end[0]),
  225. Math.min(that.tempPoints[0][1], end[1]),
  226. Math.max(that.tempPoints[0][0], end[0]),
  227. Math.max(that.tempPoints[0][1], end[1])
  228. ];
  229. that.addToMap(turf.bboxPolygon(bbox))
  230. }else if (that.drawType === 'circle' && that.tempPoints.length >= 1){
  231. let center = that.tempPoints[0];
  232. let radius = turf.distance(turf.point(center), turf.point(end), { units: 'kilometers' })
  233. that.addToMap(turf.circle(center, radius, 64))
  234. }
  235. if (that.tempPoints.length < 1 || that.drawType === 'rectangle') return;
  236. const mousePt = [e.lngLat.lng, e.lngLat.lat];
  237. const lastPt = that.tempPoints[that.tempPoints.length - 1];
  238. const line = turf.lineString([lastPt, mousePt]);
  239. that.tempLineSource.setData(line);
  240. });
  241. }
  242. startDraw(type, flag, unit) {
  243. const that = this;
  244. that.drawType = type
  245. that.isDrawing = true
  246. that.tempPoints = []
  247. that.tempLineSource.setData({ type: 'FeatureCollection', features: [] })
  248. that.itemId = 'draw-' + Date.now();
  249. that.map.getCanvas().style.cursor = 'crosshair';
  250. that.totalDistance = 0;
  251. that.unit = unit || 'kilometers';
  252. that.unitLabel = that.unit === 'kilometers' ? 'km' : 'm';
  253. flag = flag || true;
  254. that.isEditDrawTool(flag);
  255. }
  256. isEditDrawTool(flag) {
  257. const that = this;
  258. that.isEditMode = flag;
  259. if (!that.isEditMode) {
  260. that.clearEditMarkers();
  261. that.selectedFeature = null;
  262. that.selectedFeatureId = '';
  263. that.layersIds = [];
  264. }
  265. // console.log('[ isEditDrawTool ] >', that.selectedFeature)
  266. }
  267. isEdit(flag) {
  268. const that = this;
  269. that.isEditFlag = flag;
  270. if (!that.isEditFlag) {
  271. that.clearEditMarkers();
  272. that.selectedFeature = null;
  273. that.selectedFeatureId = '';
  274. that.layersIds = [];
  275. }
  276. // console.log('[ isEdit ] >', that.selectedFeature)
  277. }
  278. handleMapClick(e) {
  279. const that = this;
  280. if (!that.isDrawing) return
  281. const coord = [e.lngLat.lng, e.lngLat.lat]
  282. that.tempPoints.push(coord)
  283. if (that.drawType === 'point') {
  284. let feature = turf.point(coord)
  285. feature.properties.drawType = 'point'
  286. feature.properties.isCircle = false;
  287. that.addToMap(feature)
  288. that.finishDraw()
  289. }
  290. if (that.drawType === 'line' && that.tempPoints.length >= 2) {
  291. let feature = turf.lineString(that.tempPoints)
  292. feature.properties.drawType = 'line'
  293. feature.properties.isCircle = false;
  294. that.addToMap(feature)
  295. }
  296. if (that.drawType === 'polygon' && that.tempPoints.length >= 2) {
  297. if(that.tempPoints.length >= 3){
  298. let feature = turf.polygon([[...that.tempPoints, that.tempPoints[0]]])
  299. feature.properties.drawType = 'polygon'
  300. feature.properties.isCircle = false;
  301. that.addToMap(feature)
  302. }else{
  303. let feature = turf.lineString(that.tempPoints)
  304. feature.properties.drawType = 'polygon'
  305. feature.properties.isCircle = false;
  306. that.addToMap(feature)
  307. }
  308. }
  309. if (that.drawType === 'circle' && that.tempPoints.length >= 2) {
  310. const center = that.tempPoints[0]
  311. const radius = turf.distance(turf.point(center), turf.point(that.tempPoints[1]), { units: that.unit })
  312. let feature = turf.circle(center, radius, 64)
  313. feature.properties.drawType = 'circle'
  314. feature.properties.isCircle = true;
  315. that.addToMap(feature)
  316. that.finishDraw()
  317. }
  318. if (that.drawType === 'distance' && that.tempPoints.length >= 2) {
  319. let feature = turf.lineString(that.tempPoints)
  320. feature.properties.drawType = 'distance'
  321. feature.properties.isCircle = false;
  322. that.addToMap(feature)
  323. const distance = turf.distance(turf.point(that.tempPoints[0]), turf.point(that.tempPoints[1]), { units: that.unit })
  324. that.totalDistance += distance;
  325. that.addMarker(e.lngLat, that.totalDistance.toFixed(2) + " " + that.unitLabel);
  326. }
  327. if (that.drawType === 'area' && that.tempPoints.length >= 2) {
  328. if(that.tempPoints.length >= 3){
  329. let feature = turf.polygon([[...that.tempPoints, that.tempPoints[0]]])
  330. feature.properties.drawType = 'area'
  331. feature.properties.isCircle = false;
  332. that.addToMap(feature)
  333. const area = turf.area(feature)
  334. that.totalDistance += area;
  335. }else{
  336. let feature = turf.lineString(that.tempPoints)
  337. feature.properties.drawType = 'area'
  338. feature.properties.isCircle = false;
  339. that.addToMap(feature)
  340. }
  341. }
  342. if (that.drawType === 'rectangle' && that.tempPoints.length >= 2) {
  343. that.finishDraw()
  344. }
  345. that.updateTempLayer()
  346. }
  347. handleMapDblClick(e) {
  348. const that = this;
  349. if (!that.isDrawing || that.drawType === 'point') return
  350. if (that.drawType === 'area' && that.tempPoints.length >= 2) {
  351. if(that.tempPoints.length >= 3){
  352. let feature = turf.polygon([[...that.tempPoints, that.tempPoints[0]]])
  353. feature.properties.drawType = 'area'
  354. feature.properties.isCircle = false;
  355. that.addToMap(feature)
  356. const areaNumber = (turf.area(feature)/1000000).toFixed(2);
  357. that.addMarker(e.lngLat, areaNumber + ' 平方公里');
  358. }else{
  359. let feature = turf.lineString(that.tempPoints)
  360. feature.properties.drawType = 'area'
  361. feature.properties.isCircle = false;
  362. that.addToMap(feature)
  363. const distance = turf.distance(turf.point(that.tempPoints[0]), turf.point(that.tempPoints[1]), { units: that.unit })
  364. that.totalDistance += distance;
  365. that.addMarker(e.lngLat, that.totalDistance.toFixed(2) + " " + that.unitLabel);
  366. }
  367. }
  368. that.finishDraw()
  369. }
  370. updateTempLayer() {
  371. const that = this;
  372. let feature
  373. if (that.drawType === 'point' && that.tempPoints.length) feature = turf.point(that.tempPoints[0])
  374. else if (that.drawType === 'line' && that.tempPoints.length >= 2) feature = turf.lineString(that.tempPoints)
  375. else if (that.drawType === 'polygon' && that.tempPoints.length >= 2) feature = turf.lineString([...that.tempPoints, that.tempPoints[0]])
  376. else if (that.drawType === 'circle' && that.tempPoints.length >= 1) feature = turf.point(that.tempPoints[0])
  377. else if (that.drawType === 'distance' && that.tempPoints.length >= 2) feature = turf.lineString(that.tempPoints)
  378. else if (that.drawType === 'area' && that.tempPoints.length >= 2) feature = turf.lineString([...that.tempPoints, that.tempPoints[0]])
  379. that.tempSource?.setData(feature || { type: 'FeatureCollection', features: [] })
  380. }
  381. addMoveMarkerTips(pt, text) {
  382. const that = this;
  383. let el = document.createElement('div');
  384. el.className = 'marker-label-tips';
  385. el.innerText = text;
  386. that.moveMarkerTips = new mapboxgl.Marker(el, { anchor: 'center', offset: [0, -30] })
  387. .setLngLat(pt)
  388. .addTo(that.map);
  389. }
  390. addMarker(pt, text) {
  391. const that = this;
  392. let el = document.createElement('div');
  393. el.className = 'marker-label-tips';
  394. el.innerText = text;
  395. let marker = new mapboxgl.Marker(el, { anchor: 'center' })
  396. .setLngLat(pt)
  397. .addTo(that.map);
  398. that.drawMarkers.push(marker);
  399. }
  400. addEditMarker(coord, index) {
  401. const that = this;
  402. let el = document.createElement('div');
  403. el.className = 'edit-marker';
  404. el.style.width = '10px';
  405. el.style.height = '10px';
  406. el.style.borderRadius = '50%';
  407. el.style.backgroundColor = 'red';
  408. el.style.border = '2px solid white';
  409. el.style.cursor = 'pointer';
  410. el.addEventListener('mousedown', (e) => {
  411. e.stopPropagation();
  412. that.isDragging = true;
  413. that.draggingPointIndex = index;
  414. that.map.getCanvas().style.cursor = 'grabbing';
  415. });
  416. let marker = new mapboxgl.Marker(el)
  417. .setLngLat(coord)
  418. .addTo(that.map);
  419. that.editMarkers.push(marker);
  420. if(!that.selectedFeature.properties.isCircle){
  421. that.insertTwoMiddlePoints(marker, index);
  422. }
  423. }
  424. clearNodeMarkers() {
  425. const that = this;
  426. that.drawMarkers.forEach(m => m.remove());
  427. that.drawMarkers = [];
  428. }
  429. clearEditMarkers() {
  430. const that = this;
  431. that.editMarkers.forEach(marker => marker.remove());
  432. that.editMarkers = [];
  433. }
  434. showEditMarkers() {
  435. const that = this;
  436. if (!that.selectedFeature) return;
  437. const feature = that.selectedFeature;
  438. if (feature.type === 'Feature') {
  439. if (feature.geometry.type === 'Point') {
  440. that.addEditMarker(feature.geometry.coordinates, 0);
  441. } else if (feature.geometry.type === 'LineString') {
  442. feature.geometry.coordinates.forEach((coord, index) => {
  443. that.addEditMarker(coord, index);
  444. });
  445. } else if (feature.geometry.type === 'Polygon') {
  446. feature.geometry.coordinates[0].forEach((coord, index) => {
  447. if(index == feature.geometry.coordinates[0].length - 1){
  448. return;
  449. }
  450. that.addEditMarker(coord, index);
  451. });
  452. }
  453. }
  454. }
  455. clearAll() {
  456. const that = this;
  457. that.clearNodeMarkers();
  458. that.clearEditMarkers();
  459. that.selectedFeature = null;
  460. that.selectedFeatureId = '';
  461. that.layersIds = [];
  462. const layers = that.map.getStyle().layers;
  463. layers.sort((a, b) => a.type.localeCompare(b.type));
  464. layers.forEach(layer => {
  465. if (layer.id.startsWith('draw-')) {
  466. if(layer.id.indexOf("-point") !== -1){
  467. that.map.removeLayer(layer.id)
  468. }
  469. if(layer.id.indexOf("-line") !== -1){
  470. that.map.removeLayer(layer.id)
  471. let ids = layer.id.replace("-line",'');
  472. that.map.removeSource(ids)
  473. }
  474. if(layer.id.indexOf("-polygon") !== -1){
  475. that.map.removeLayer(layer.id)
  476. }
  477. }
  478. })
  479. }
  480. addToMap(feature) {
  481. const that = this;
  482. let id = that.itemId;
  483. if (!that.map.getSource(id)) {
  484. that.map.addSource(id, { type: 'geojson', data: feature })
  485. if(that.drawType === 'line'){
  486. that.map.addLayer({ id: id + '-line', type: 'line', source: id, paint: { 'line-color': 'red', 'line-width': 2 } })
  487. }else if(that.drawType === 'polygon'){
  488. if(that.tempPoints.length >= 3){
  489. that.map.addLayer({ id: id + '-polygon', type: 'fill', source: id, paint: { 'fill-color': 'red', 'fill-opacity': 0.3 }})
  490. }
  491. that.map.addLayer({ id: id + '-line', type: 'line', source: id, paint: { 'line-color': 'red', 'line-width': 2 } })
  492. }else if(that.drawType === 'circle'){
  493. that.map.addLayer({ id: id + '-polygon', type: 'fill', source: id, paint: { 'fill-color': 'red', 'fill-opacity': 0.3 }})
  494. that.map.addLayer({ id: id + '-line', type: 'line', source: id, paint: { 'line-color': 'red', 'line-width': 2 } })
  495. }else if(that.drawType === 'distance'){
  496. that.map.addLayer({ id: id + '-line', type: 'line', source: id, paint: { 'line-color': 'red', 'line-width': 2 } })
  497. }else if(that.drawType === 'area'){
  498. if(that.tempPoints.length >= 3){
  499. that.map.addLayer({ id: id + '-polygon', type: 'fill', source: id, paint: { 'fill-color': 'red', 'fill-opacity': 0.3 }})
  500. }
  501. that.map.addLayer({ id: id + '-line', type: 'line', source: id, paint: { 'line-color': 'red', 'line-width': 2 } })
  502. }else if(that.drawType === 'rectangle'){
  503. that.map.addLayer({ id: id + '-polygon', type: 'fill', source: id, paint: { 'fill-color': 'red', 'fill-opacity': 0.3 }})
  504. that.map.addLayer({ id: id + '-line', type: 'line', source: id, paint: { 'line-color': 'red', 'line-width': 2 } })
  505. }else{
  506. that.map.addLayer({ id: id + '-point', type: 'circle', source: id, paint: { 'circle-radius': 5, 'circle-color': 'red' } })
  507. }
  508. }else{
  509. that.map.getSource(id).setData(feature)
  510. if(that.tempPoints.length >= 3 && (that.drawType === 'area' || that.drawType === 'polygon')){
  511. if(!that.map.getLayer(id + '-polygon')){
  512. that.map.addLayer({ id: id + '-polygon', type: 'fill', source: id, paint: { 'fill-color': 'red', 'fill-opacity': 0.3 }})
  513. }
  514. }
  515. }
  516. that.tempData = feature;
  517. }
  518. finishDraw() {
  519. const that = this;
  520. that.getCompleteFeature(that.tempData);
  521. that.isDrawing = false
  522. that.drawType = ''
  523. that.tempPoints = []
  524. that.tempSource?.setData({ type: 'FeatureCollection', features: [] })
  525. that.tempLineSource.setData({ type: 'FeatureCollection', features: [] })
  526. that.map.getCanvas().style.cursor = '';
  527. if(that.moveMarkerTips){
  528. that.moveMarkerTips.remove();
  529. that.moveMarkerTips=null;
  530. }
  531. }
  532. handleFeatureClick(e) {
  533. const that = this;
  534. const drawLayers = that.map.getStyle().layers
  535. .filter(layer => layer.id.startsWith('draw-'))
  536. .map(layer => layer.id);
  537. const point = e.point;
  538. const buffer = 5;
  539. const box = [
  540. [point.x - buffer, point.y - buffer],
  541. [point.x + buffer, point.y + buffer]
  542. ];
  543. // 追加自定义绘制图层
  544. that.editLayerIds.forEach(layerId => {
  545. drawLayers.push(layerId);
  546. })
  547. const features = that.map.queryRenderedFeatures(box, {
  548. layers: drawLayers
  549. });
  550. // console.log('[ =====handleFeatureClick===== ] >', that.selectedFeature)
  551. if (features.length > 0) {
  552. if(that.isEditFlag){// 编辑绘制
  553. const feature = features[0];
  554. const sourceId = feature.layer.source;
  555. if (!that.map.getSource(sourceId)) return;
  556. that.selectedFeatureId = sourceId;
  557. const sourceData = that.map.getSource(sourceId)._data;
  558. if(sourceData.geometry){
  559. that.selectedFeature = JSON.parse(JSON.stringify(sourceData));
  560. }else{
  561. sourceData.features.forEach(item => {
  562. if(item.properties.dms_id === feature.properties.dms_id){
  563. that.selectedFeature = JSON.parse(JSON.stringify(item));
  564. return;
  565. }
  566. })
  567. }
  568. // 深拷贝切断引用
  569. // that.selectedFeature = JSON.parse(JSON.stringify(feature));
  570. that.getCompleteFeature(that.selectedFeature);
  571. that.clearEditMarkers();
  572. if(that.selectedFeature.properties.drawType === 'distance' || that.selectedFeature.properties.drawType === 'area') return;
  573. that.showEditMarkers();
  574. }else{ // 编辑工具绘制
  575. const feature = features[0];
  576. const layerId = feature.layer.id;
  577. const featureId = layerId.split('-').slice(0, -1).join('-');
  578. if (!that.map.getSource(featureId)) return;
  579. that.selectedFeatureId = featureId;
  580. const sourceData = that.map.getSource(featureId)._data;
  581. that.selectedFeature = JSON.parse(JSON.stringify(sourceData));
  582. // console.log('[ handleFeatureClick ] >', that.selectedFeature)
  583. that.clearEditMarkers();
  584. if(that.selectedFeature.properties.drawType === 'distance' || that.selectedFeature.properties.drawType === 'area') return;
  585. that.showEditMarkers();
  586. }
  587. } else {
  588. that.clearEditMarkers();
  589. that.selectedFeature = null;
  590. that.selectedFeatureId = '';
  591. // console.log('[ handleFeatureClick======== ] >', that.selectedFeature)
  592. }
  593. }
  594. // 编辑绘制点击事件 弃用 已被 handleFeatureClick 替代
  595. handleEditDrawClick(e) {
  596. const that = this;
  597. const point = e.point;
  598. const buffer = 5;
  599. const box = [
  600. [point.x - buffer, point.y - buffer],
  601. [point.x + buffer, point.y + buffer]
  602. ];
  603. const features = that.map.queryRenderedFeatures(box, {
  604. layers: that.editLayerIds
  605. });
  606. // console.log('[ handleEditDrawClick ] >', features)
  607. if (features.length > 0) {
  608. const feature = features[0];
  609. const sourceId = feature.layer.source;
  610. if (!that.map.getSource(sourceId)) return;
  611. that.selectedFeatureId = sourceId;
  612. const sourceData = that.map.getSource(sourceId)._data;
  613. if(sourceData.geometry){
  614. that.selectedFeature = JSON.parse(JSON.stringify(sourceData));
  615. }else{
  616. sourceData.features.forEach(item => {
  617. if(item.properties.dms_id === feature.properties.dms_id){
  618. that.selectedFeature = JSON.parse(JSON.stringify(item));
  619. return;
  620. }
  621. })
  622. }
  623. // 深拷贝切断引用
  624. // that.selectedFeature = JSON.parse(JSON.stringify(feature));
  625. that.getCompleteFeature(that.selectedFeature);
  626. that.clearEditMarkers();
  627. that.showEditMarkers();
  628. } else {
  629. that.selectedFeature = null;
  630. that.selectedFeatureId = '';
  631. }
  632. }
  633. moveWholeFeature(dx, dy) {
  634. const that = this;
  635. if (!that.selectedFeature) return;
  636. const feature = that.selectedFeature;
  637. if (feature.geometry.type === 'Point') {
  638. feature.geometry.coordinates[0] += dx;
  639. feature.geometry.coordinates[1] += dy;
  640. } else if (feature.geometry.type === 'LineString') {
  641. feature.geometry.coordinates.forEach(coord => {
  642. coord[0] += dx;
  643. coord[1] += dy;
  644. });
  645. } else if (feature.geometry.type === 'Polygon') {
  646. if(feature.properties.isCircle){
  647. const centerPt = turf.centroid(feature);
  648. const center = centerPt.geometry.coordinates;
  649. const newCenter = [center[0] + dx, center[1] + dy];
  650. const radiusMeters = turf.distance(center, feature.geometry.coordinates[0][0], { units: 'meters' });
  651. const newCircle = turf.circle(newCenter, radiusMeters, {
  652. units: 'meters',
  653. steps: 64
  654. });
  655. newCircle.properties = feature.properties;
  656. that.selectedFeature = newCircle;
  657. } else {
  658. feature.geometry.coordinates.forEach(ring => {
  659. ring.forEach(coord => {
  660. coord[0] += dx;
  661. coord[1] += dy;
  662. });
  663. });
  664. }
  665. }
  666. that.map.getSource(that.selectedFeatureId).setData(that.selectedFeature);
  667. const sourceData = that.map.getSource(that.selectedFeatureId)._data;
  668. that.selectedFeature = JSON.parse(JSON.stringify(sourceData));
  669. }
  670. editCircleVertex(newPoint) {
  671. const that = this;
  672. const feature = that.selectedFeature;
  673. const centerPt = turf.centroid(feature);
  674. const center = centerPt.geometry.coordinates;
  675. const radiusMeters = turf.distance(center, newPoint, { units: 'meters' });
  676. if (radiusMeters < 5) return;
  677. const newCircle = turf.circle(center, radiusMeters, {
  678. units: 'meters',
  679. steps: 64
  680. });
  681. newCircle.properties = feature.properties;
  682. const line = turf.lineString([center, newPoint]);
  683. that.tempLineSource.setData(line);
  684. that.selectedFeature = newCircle;
  685. return newCircle;
  686. }
  687. insertTwoMiddlePoints(marker, index) {
  688. const that = this;
  689. marker.getElement().addEventListener('click', (e) => {
  690. e.stopPropagation();
  691. if (!that.selectedFeature) return;
  692. const geom = that.selectedFeature.geometry;
  693. let coords = [];
  694. let isPolygon = false;
  695. if (geom.type === 'LineString') {
  696. coords = [...geom.coordinates];
  697. } else if (geom.type === 'Polygon') {
  698. coords = [...geom.coordinates[0]];
  699. isPolygon = true;
  700. }
  701. if (coords.length < 2) return;
  702. if (isPolygon) {
  703. // ======================
  704. // 面对称加两点(左边1个 + 右边1个)
  705. // ======================
  706. let pureCoords = coords.slice(0, -1);
  707. const total = pureCoords.length;
  708. let realIndex = index;
  709. if (index === coords.length - 1) {
  710. realIndex = 0;
  711. }
  712. const prevIdx = (realIndex - 1 + total) % total;
  713. const nextIdx = (realIndex + 1) % total;
  714. const prevPt = pureCoords[prevIdx];
  715. const currPt = pureCoords[realIndex];
  716. const nextPt = pureCoords[nextIdx];
  717. const midPrev = [(prevPt[0] + currPt[0]) / 2, (prevPt[1] + currPt[1]) / 2];
  718. const midNext = [(currPt[0] + nextPt[0]) / 2, (currPt[1] + nextPt[1]) / 2];
  719. pureCoords.splice(realIndex, 0, midPrev);
  720. pureCoords.splice(realIndex + 2, 0, midNext);
  721. coords = [...pureCoords, pureCoords[0]];
  722. geom.coordinates[0] = coords;
  723. } else {
  724. // ======================
  725. // 线和面对称加两点(左边1个 + 右边1个)
  726. // ======================
  727. if (index === 0) {
  728. // 起点 → 只在右侧加点(和面对齐)
  729. const nextPt = coords[index + 1];
  730. const midNext = [(coords[index][0] + nextPt[0]) / 2, (coords[index][1] + nextPt[1]) / 2];
  731. coords.splice(index + 1, 0, midNext);
  732. }
  733. else if (index === coords.length - 1) {
  734. // 终点 → 只在左侧加点
  735. const prevPt = coords[index - 1];
  736. const midPrev = [(prevPt[0] + coords[index][0]) / 2, (prevPt[1] + coords[index][1]) / 2];
  737. coords.splice(index, 0, midPrev);
  738. }
  739. else {
  740. // 中间点 → 左右各加 1 点(和面完全一样!)
  741. const prevPt = coords[index - 1];
  742. const nextPt = coords[index + 1];
  743. const currPt = coords[index];
  744. const midPrev = [(prevPt[0] + currPt[0]) / 2, (prevPt[1] + currPt[1]) / 2];
  745. const midNext = [(currPt[0] + nextPt[0]) / 2, (currPt[1] + nextPt[1]) / 2];
  746. coords.splice(index, 0, midPrev);
  747. coords.splice(index + 2, 0, midNext);
  748. }
  749. geom.coordinates = coords;
  750. }
  751. that.refreshFeature();
  752. });
  753. }
  754. // ========== 键盘 DEL 删除选中节点 / 图形 ==========
  755. bindDeleteHotkey() {
  756. const that = this;
  757. // 先清掉旧事件,防止多实例、上下文错乱
  758. window.removeEventListener('keydown', that._deleteKeyHandler);
  759. // 保存成实例方法,保证永远指向最新实例
  760. that._deleteKeyHandler = function (e) {
  761. // 🔥 这里用 that,永远是当前最新实例
  762. if (e.key === 'Delete' && that.selectedFeature && (that.isEditMode || that.isEditFlag)) {
  763. e.preventDefault();
  764. // console.log('DEL 触发 - 当前图形:', that.selectedFeature);
  765. // 如果正在编辑、有选中节点 → 删除当前节点
  766. if (that.draggingPointIndex !== -1) {
  767. that.deleteVertex(that.draggingPointIndex);
  768. }
  769. // else {
  770. // that.deleteSelected();
  771. // }
  772. }
  773. };
  774. window.addEventListener('keydown', that._deleteKeyHandler);
  775. }
  776. // ========== 删除顶点(点/线/面 通用) ==========
  777. deleteVertex(index) {
  778. const that = this;
  779. if (!that.selectedFeature) return;
  780. const geom = that.selectedFeature.geometry;
  781. let coords;
  782. let isPolygon = false;
  783. // 先重置拖拽状态!防止索引错乱
  784. that.draggingPointIndex = -1;
  785. that.isDragging = false;
  786. if (geom.type === 'LineString') {
  787. coords = geom.coordinates;
  788. if (coords.length <= 2) return; // 线至少保留2个点
  789. }
  790. else if (geom.type === 'Polygon') {
  791. isPolygon = true;
  792. coords = geom.coordinates[0];
  793. if (coords.length <= 4) return; // 面至少保留3个点
  794. }
  795. // 删除顶点
  796. coords.splice(index, 1);
  797. // 闭合面处理
  798. if (isPolygon) {
  799. if (index === 0) {
  800. coords[coords.length - 1] = coords[0];
  801. }
  802. }
  803. // 刷新
  804. that.refreshFeature();
  805. }
  806. refreshFeature() {
  807. const that = this;
  808. if (!that.selectedFeature || !that.selectedFeatureId) return;
  809. // ========== 必须重置拖拽状态!否则必乱 ==========
  810. that.draggingPointIndex = -1;
  811. that.isDragging = false;
  812. that.map.getSource(that.selectedFeatureId).setData(that.selectedFeature);
  813. that.selectedFeature = JSON.parse(JSON.stringify(that.map.getSource(that.selectedFeatureId)._data));
  814. that.clearEditMarkers();
  815. that.showEditMarkers();
  816. }
  817. handleDrag(e) {
  818. const that = this;
  819. // console.log("handleDrag",that.draggingPointIndex);
  820. if (!that.selectedFeature || that.draggingPointIndex === -1) return;
  821. if(that.selectedFeature.properties.drawType === 'distance' || that.selectedFeature.properties.drawType === 'area') return;
  822. let feature = that.selectedFeature;
  823. let newCoord = [e.lngLat.lng, e.lngLat.lat];
  824. if(that.selectedFeature.properties.isCircle){
  825. that.clearEditMarkers();
  826. feature = that.editCircleVertex(newCoord);
  827. }else{
  828. if (feature.geometry.type === 'Point') {
  829. feature.geometry.coordinates = newCoord;
  830. } else if (feature.geometry.type === 'LineString') {
  831. feature.geometry.coordinates[that.draggingPointIndex] = newCoord;
  832. } else if (feature.geometry.type === 'Polygon') {
  833. feature.geometry.coordinates[0][that.draggingPointIndex] = newCoord;
  834. if (that.draggingPointIndex === 0) {
  835. feature.geometry.coordinates[0][feature.geometry.coordinates[0].length - 1] = newCoord;
  836. }
  837. }
  838. that.clearEditMarkers();
  839. that.showEditMarkers();
  840. }
  841. that.selectedFeature = JSON.parse(JSON.stringify(feature));
  842. that.map.getSource(that.selectedFeatureId).setData(feature);
  843. that.getCompleteFeature(that.selectedFeature);
  844. // console.log("handleDrag selectedFeature",that.selectedFeature);
  845. }
  846. setEditLayerIds(arr){
  847. const that = this;
  848. that.editLayerIds = arr;
  849. }
  850. deleteSelected() {
  851. let that = this;
  852. if (!that.selectedFeatureId) return;
  853. const layers = window.mapboxMap.getStyle().layers;
  854. layers.forEach(layer => {
  855. if (layer.id.startsWith(that.selectedFeatureId)) {
  856. that.map.removeLayer(layer.id);
  857. }
  858. });
  859. that.map.removeSource(that.selectedFeatureId);
  860. that.clearEditMarkers();
  861. that.selectedFeature = null;
  862. that.selectedFeatureId = '';
  863. that.layersIds = [];
  864. }
  865. }
  866. function init(map, options) {
  867. const instance = new MapDraw(map, options);
  868. return {
  869. startDraw: (type, flag, unit) => instance.startDraw(type, flag, unit),
  870. isEdit: (flag) => instance.isEdit(flag),
  871. clearAll: () => instance.clearAll(),
  872. setEditLayerIds: (arr) => instance.setEditLayerIds(arr),
  873. };
  874. }
  875. window.MapDraw = { init };
  876. })(window);