base-draw.ts 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. import * as ol from 'ol'
  2. import * as style from 'ol/style'
  3. import * as layer from 'ol/layer'
  4. import * as source from 'ol/source'
  5. import * as geom from 'ol/geom'
  6. import * as extent from 'ol/extent'
  7. import * as proj from 'ol/proj'
  8. import * as interaction from 'ol/interaction'
  9. import * as format from "ol/format";
  10. import Modify from "ol/interaction/Modify"
  11. import Draw, {createBox} from "ol/interaction/Draw"
  12. import * as sphere from "ol/sphere";
  13. import {unByKey} from "ol/Observable";
  14. import {formatPosition} from "@/utils/easyMap";
  15. import {isValue} from "@/utils/util";
  16. import {fromCircle} from "ol/geom/Polygon";
  17. import store from "@/store";
  18. const globalLineDash = [
  19. [0, 0], //实线
  20. [15, 15], //长虚线
  21. [5, 5] //虚线
  22. ]
  23. const layerFlag = ['layerName', 'drawViewsLayer']
  24. const drawFlag = ['interactionName', 'drawInteraction']
  25. const modifyFlag = ['interactionName', 'modifyInteraction']
  26. const baseDrawConfig = {
  27. // 样式字段
  28. text: null, // 要素上显示的文字,默认无文字
  29. pointIcon: null, // Point的图标,默认圆形
  30. pointScale: 1, // Point的缩放,默认1
  31. pointOffset: [0, 0], // Point的偏移量,默认[0, 0]
  32. lineColor: '#2860F1', // LineString的线段颜色,默认蓝色
  33. lineWidth: 1, // LineString的线段宽度,默认1
  34. lineType: 0, // LineString的线段类型索引,默认0,实线,取globalLineDash数组索引
  35. lineDash: null, // LineString的线段类型,默认null,优先级比lineType高
  36. polyColor: 'rgba(20, 129, 241, 0.1)', // Polygon的填充色,默认蓝色
  37. polyBorderColor: '#2860F1', // Polygon的边框颜色,默认蓝色
  38. polyBorderWidth: 1, // Polygon的边框宽度,默认1
  39. polyBorderType: 0, // Polygon的边框类型索引,默认0,实线,取globalLineDash数组索引
  40. polyBorderDash: null, // Polygon的边框类型,默认null,优先级比polyBorderType高
  41. // 业务字段
  42. show: false, // 标绘样式选项是否显示
  43. featureType: 'Point', // 标绘的要素类型
  44. isPoint: false, // 是否可以标绘点
  45. isLineString: false, // 是否可以标绘线
  46. isPolygon: false, // 是否可以标绘面
  47. showPosition: false, // 是否显示经纬度输入框
  48. refreshStyleFunc: () => {}, // 刷新标绘样式方法
  49. }
  50. export const getBaseDrawConfig = () => {
  51. return JSON.parse(JSON.stringify(baseDrawConfig))
  52. }
  53. /**
  54. *
  55. * @param map
  56. * @param arr 要标绘的数组,每个对象的数据在要素中保存的属性为 'val'
  57. * @param arr > wkt:wkt格式坐标
  58. * @param arr > styles:要素的样式,优先级最高
  59. * @param arr > text:styles为空的时候,要素上显示的文字,默认无文字
  60. * @param arr > textOffsetY:styles为空的时候,要素上显示的文字Y轴偏移量,默认Point-30,其他0
  61. * @param arr > pointIcon:styles为空的时候,Point的图标,默认圆形
  62. * @param arr > pointScale:styles为空的时候,Point的缩放,默认1
  63. * @param arr > pointOffset:styles为空的时候,Point的偏移量,默认[0, 0]
  64. * @param arr > lineColor:styles为空的时候,LineString的线段颜色,默认蓝色
  65. * @param arr > lineWidth:styles为空的时候,LineString的线段宽度,默认1
  66. * @param arr > lineType:styles为空的时候,LineString的线段类型索引,默认0,实线,取globalLineDash数组索引
  67. * @param arr > lineDash:styles为空的时候,LineString的线段类型,默认null,优先级比lineType高
  68. * @param arr > polyColor:styles为空的时候,Polygon的填充色,默认蓝色
  69. * @param arr > polyBorderColor:styles为空的时候,Polygon的边框颜色,默认蓝色
  70. * @param arr > polyBorderWidth:styles为空的时候,Polygon的边框宽度,默认1
  71. * @param arr > polyBorderType:styles为空的时候,Polygon的边框类型索引,默认0,实线,取globalLineDash数组索引
  72. * @param arr > polyBorderDash:styles为空的时候,Polygon的边框类型,默认null,优先级比polyBorderType高
  73. */
  74. export const drawViews = (map, arr) => {
  75. let _source
  76. const realLayer = map.getLayers().getArray().filter(v => v.get(layerFlag[0]) === layerFlag[1])
  77. if (realLayer[0]) {
  78. _source = realLayer[0].getSource()
  79. } else {
  80. _source = new source.Vector(); //图层数据源
  81. const _vector = new layer.Vector({
  82. zIndex: 9999,
  83. source: _source,
  84. });
  85. _vector.set(layerFlag[0], layerFlag[1])
  86. map.addLayer(_vector);
  87. }
  88. _source.clear() // 清空标绘
  89. const features = arr.filter(v => {
  90. try {
  91. new format.WKT().readFeature(v.wkt)
  92. return true
  93. } catch (e) {
  94. console.error('错误坐标:' + v.wkt)
  95. return false
  96. }
  97. }).map(v => {
  98. const feat: any = new format.WKT().readFeature(v.wkt)
  99. const type = feat.getGeometry().getType()
  100. const {
  101. textOffsetY = (type === 'Point' ? -30 : 0),
  102. pointIcon, pointScale, pointOffset,
  103. lineColor, lineWidth, lineType, lineDash,
  104. polyColor, polyBorderColor, polyBorderWidth, polyBorderType, polyBorderDash
  105. } = Object.assign(getBaseDrawConfig(), v)
  106. let styles: any = []
  107. if (v.styles) {
  108. styles = v.styles
  109. } else {
  110. if (type === 'Point') {
  111. if (pointIcon) {
  112. styles.push(new style.Style({
  113. image: new style.Icon({
  114. src: pointIcon,
  115. scale: pointScale,
  116. displacement: pointOffset
  117. })
  118. }))
  119. } else {
  120. styles.push(new style.Style({
  121. image: new style.Circle({
  122. radius: 10,
  123. fill: new style.Fill({
  124. color: '#e810dd',
  125. }),
  126. scale: pointScale,
  127. displacement: pointOffset
  128. })
  129. }))
  130. }
  131. } else if (type === 'LineString') {
  132. styles.push(new style.Style({
  133. stroke: new style.Stroke({
  134. color: lineColor,
  135. width: lineWidth,
  136. lineDash: lineDash ?? globalLineDash[Number(lineType)]
  137. })
  138. }))
  139. } else if (type === 'Polygon') {
  140. styles.push(new style.Style({
  141. stroke: new style.Stroke({
  142. color: polyBorderColor,
  143. width: polyBorderWidth,
  144. lineDash: polyBorderDash ?? globalLineDash[Number(polyBorderType)]
  145. }),
  146. fill: new style.Fill({
  147. color: polyColor,
  148. }),
  149. }))
  150. }
  151. if (v.text) {
  152. styles.push(new style.Style({
  153. text: new style.Text({
  154. font: "16px bold 微软雅黑",
  155. text: v.text,
  156. fill: new style.Fill({
  157. color: '#ffffff'
  158. }),
  159. stroke: new style.Stroke({
  160. color: '#D26CDB',
  161. width: 2
  162. }),
  163. offsetY: textOffsetY,
  164. }),
  165. }))
  166. }
  167. }
  168. feat.set('val', v)
  169. feat.setStyle(styles)
  170. return feat
  171. })
  172. _source.addFeatures(features)
  173. }
  174. let baseDrawTooltipElement;
  175. let baseDrawHelpTooltipElement;
  176. export const drawEdits = (map, obj, emitWkt) => {
  177. return new Promise((resolve => {
  178. if (!isValue(obj.textOffsetY)) {
  179. obj.textOffsetY = (obj.featureType === 'Point' ? -30 : 0)
  180. }
  181. let commonStyle = (showText) => {
  182. if (obj.featureType === 'Point') {
  183. return new style.Style({
  184. image: obj.pointIcon ? new style.Icon({
  185. src: obj.pointIcon,
  186. scale: obj.pointScale,
  187. displacement: obj.pointOffset
  188. }) : new style.Circle({
  189. radius: 10,
  190. fill: new style.Fill({
  191. color: '#e810dd',
  192. }),
  193. scale: obj.pointScale,
  194. displacement: obj.pointOffset
  195. }),
  196. text: (showText && obj.text) ? new style.Text({
  197. font: "16px bold 微软雅黑",
  198. text: obj.text,
  199. fill: new style.Fill({
  200. color: '#ffffff'
  201. }),
  202. stroke: new style.Stroke({
  203. color: '#D26CDB',
  204. width: 2
  205. }),
  206. offsetY: obj.textOffsetY,
  207. }) : undefined,
  208. })
  209. } else if (obj.featureType === 'LineString') {
  210. return new style.Style({
  211. stroke: new style.Stroke({
  212. color: obj.lineColor,
  213. width: obj.lineWidth,
  214. lineDash: obj.lineDash ?? globalLineDash[Number(obj.lineType)]
  215. }),
  216. text: (showText && obj.text) ? new style.Text({
  217. font: "16px bold 微软雅黑",
  218. text: obj.text,
  219. fill: new style.Fill({
  220. color: '#ffffff'
  221. }),
  222. stroke: new style.Stroke({
  223. color: '#D26CDB',
  224. width: 2
  225. }),
  226. offsetY: obj.textOffsetY,
  227. }) : undefined,
  228. })
  229. } else if (obj.featureType === 'Polygon') {
  230. return new style.Style({
  231. stroke: new style.Stroke({
  232. color: obj.polyBorderColor,
  233. width: obj.polyBorderWidth,
  234. lineDash: obj.polyBorderDash ?? globalLineDash[Number(obj.polyBorderType)]
  235. }),
  236. fill: new style.Fill({
  237. color: obj.polyColor,
  238. }),
  239. text: (showText && obj.text) ? new style.Text({
  240. font: "16px bold 微软雅黑",
  241. text: obj.text,
  242. fill: new style.Fill({
  243. color: '#ffffff'
  244. }),
  245. stroke: new style.Stroke({
  246. color: '#D26CDB',
  247. width: 2
  248. }),
  249. offsetY: obj.textOffsetY,
  250. }) : undefined,
  251. })
  252. }
  253. }
  254. const getWkt = (feature) => {
  255. const gm = feature.getGeometry()
  256. let wkt = ''
  257. const gType = gm.getType()
  258. if (gType === 'Polygon') {
  259. wkt = formatPosition.cpnTwpn(gm.getCoordinates())
  260. } else if (gType === 'LineString') {
  261. wkt = formatPosition.clTwl(gm.getCoordinates())
  262. } else if (gType === 'Circle') {
  263. const circlePoly = fromCircle(gm, 128)
  264. feature.setGeometry(circlePoly)
  265. wkt = formatPosition.cpnTwpn(circlePoly.getCoordinates())
  266. } else if (gType === 'Point') {
  267. wkt = formatPosition.cptTwpt(gm.getCoordinates())
  268. }
  269. emitWkt(wkt)
  270. }
  271. const reset = () => {
  272. const oldLayer = map.getLayers().getArray().filter(v => v.get(layerFlag[0]) === layerFlag[1])
  273. if (oldLayer) {
  274. map.removeLayer(oldLayer[0])
  275. }
  276. const oldDraw = map.getInteractions().getArray().filter(v => v.get(drawFlag[0]) === drawFlag[1])
  277. if (oldDraw) {
  278. map.removeInteraction(oldDraw[0])
  279. }
  280. const oldModify = map.getInteractions().getArray().filter(v => v.get(modifyFlag[0]) === modifyFlag[1])
  281. if (oldModify) {
  282. map.removeInteraction(oldModify[0])
  283. }
  284. }
  285. if (!baseDrawTooltipElement) {
  286. reset()
  287. if (obj.wkt) {
  288. drawViews(map, [obj])
  289. }
  290. let _source
  291. const realLayer = map.getLayers().getArray().filter(v => v.get(layerFlag[0]) === layerFlag[1])
  292. if (realLayer[0]) {
  293. _source = realLayer[0].getSource()
  294. } else {
  295. _source = new source.Vector(); //图层数据源
  296. const _vector = new layer.Vector({
  297. zIndex: 9999,
  298. source: _source,
  299. style: commonStyle(true),
  300. });
  301. _vector.set(layerFlag[0], layerFlag[1])
  302. map.addLayer(_vector);
  303. }
  304. const modifyInteraction = new Modify({
  305. source: _source,
  306. });
  307. modifyInteraction.set(modifyFlag[0], modifyFlag[1])
  308. map.addInteraction(modifyInteraction)
  309. modifyInteraction.on('modifyend', evt => {
  310. try {
  311. const feat = evt.features.item(0)
  312. getWkt(feat)
  313. } catch {
  314. }
  315. })
  316. if (!obj.wkt) {
  317. let sketch;
  318. let helpTooltip;
  319. let measureTooltip;
  320. let continueMsg = '双击结束标绘';
  321. const geodesicCheckbox = true;//测地学方式对象
  322. const createMeasureTooltip = () => {
  323. const id = 'baseDrawTooltipElementId'
  324. if (baseDrawTooltipElement) {
  325. map.removeOverlay(map.getOverlayById(id))
  326. baseDrawTooltipElement.parentNode.removeChild(baseDrawTooltipElement);
  327. }
  328. baseDrawTooltipElement = document.createElement('div');
  329. baseDrawTooltipElement.className = 'tooltip tooltip-measure';
  330. measureTooltip = new ol.Overlay({
  331. id,
  332. element: baseDrawTooltipElement,
  333. offset: [0, -15],
  334. positioning: 'bottom-center'
  335. });
  336. map.addOverlay(measureTooltip);
  337. }
  338. const createHelpTooltip = () => {
  339. const id = 'baseDrawHelpTooltipElementId'
  340. if (baseDrawHelpTooltipElement) {
  341. map.removeOverlay(map.getOverlayById(id))
  342. baseDrawHelpTooltipElement.parentNode.removeChild(baseDrawHelpTooltipElement);
  343. }
  344. baseDrawHelpTooltipElement = document.createElement('div');
  345. baseDrawHelpTooltipElement.className = 'tooltip hidden';
  346. helpTooltip = new ol.Overlay({
  347. id,
  348. element: baseDrawHelpTooltipElement,
  349. offset: [15, 0],
  350. positioning: 'center-left'
  351. });
  352. map.addOverlay(helpTooltip);
  353. }
  354. const formatLength = (line) => {
  355. // 获取投影坐标系
  356. const sourceProj = map.getView().getProjection();
  357. // ol/sphere里有getLength()和getArea()用来测量距离和区域面积,默认的投影坐标系是EPSG:3857, 其中有个options的参数,可以设置投影坐标系
  358. const length = sphere.getLength(line, {projection: sourceProj});
  359. // const length = getLength(line);
  360. let output;
  361. if (length > 100) {
  362. const km = Math.round((length / 1000) * 100) / 100;
  363. output = `${km} 千米 <br>${parseFloat(String(km * 0.53995)).toFixed(2)} 海里`;
  364. } else {
  365. output = `${Math.round(length * 100) / 100} m`;
  366. }
  367. return output;
  368. };
  369. //获取圆的面积
  370. const getCircleArea = (circle, projection) => {
  371. const P = 3.14
  372. const radius = getCircleRadius(circle, projection)
  373. return P * radius * radius
  374. }
  375. //获取圆的半径
  376. const getCircleRadius = (circle, projection) => {
  377. return circle.getRadius() * projection.getMetersPerUnit()
  378. }
  379. const formatArea = (polygon, type= 'polygon') => {
  380. let area
  381. const sourceProj = map.getView().getProjection();
  382. // 获取投影坐标系
  383. if (type === 'polygon') {
  384. area = sphere.getArea(polygon, {
  385. projection: sourceProj,
  386. });
  387. } else if (type === 'circle') {
  388. area = getCircleArea(polygon, sourceProj)
  389. }
  390. let output;
  391. if (area > 10000) {
  392. const km = Math.round((area / 1000000) * 100) / 100;
  393. output = `${km} 平方公里<br>${parseFloat(String(km * 0.38610)).toFixed(
  394. 2
  395. )} 平方英里`;
  396. } else {
  397. output = `${Math.round(area * 100) / 100} ` + " m<sup>2</sup>";
  398. }
  399. return output;
  400. };
  401. const addInteraction = () => {
  402. const id = 'baseDrawName'
  403. const draw = new interaction.Draw({
  404. source: _source,//测量绘制层数据源
  405. type: obj.featureType, //几何图形类型
  406. // geometryFunction: typeSelect === 'rectangle' ? createBox() : null,
  407. style: commonStyle(obj.featureType === 'Point' ? true : false),
  408. });
  409. draw.set('showText', obj.featureType === 'Point' ? true : false)
  410. draw.set(drawFlag[0], drawFlag[1])
  411. draw.set(id, id)
  412. createMeasureTooltip(); //创建测量工具提示框
  413. createHelpTooltip(); //创建帮助提示框
  414. map.addInteraction(draw);
  415. let listener;
  416. //绑定交互绘制工具开始绘制的事件
  417. const drawstartHandle = (evt) => {
  418. store.commit('gis/SET_IS_DRAWING', true)
  419. sketch = evt.feature; //绘制的要素
  420. let tooltipCoord = evt.coordinate;// 绘制的坐标
  421. //绑定change事件,根据绘制几何类型得到测量长度值或面积值,并将其设置到测量工具提示框中显示
  422. listener = sketch.getGeometry().on('change', function (evt) {
  423. const geom = evt.target
  424. let output;
  425. if (geom.getType() === 'LineString') {
  426. output = formatLength(geom);//长度值
  427. tooltipCoord = geom.getLastCoordinate();//坐标
  428. } else if (geom.getType() === 'Polygon') {
  429. output = formatArea(geom);//面积值
  430. tooltipCoord = geom.getInteriorPoint().getCoordinates();//坐标
  431. } else if (geom.getType() === 'Circle') {
  432. output = formatArea(geom, 'circle');//面积值
  433. tooltipCoord = geom.getCenter()
  434. }
  435. baseDrawTooltipElement.innerHTML = output;//将测量值设置到测量工具提示框中显示
  436. measureTooltip.setPosition(tooltipCoord);//设置测量工具提示框的显示位置
  437. });
  438. }
  439. draw.on('drawstart', drawstartHandle);
  440. //绑定交互绘制工具结束绘制的事件
  441. const copy = (value) => {
  442. const str = document.createElement('input')
  443. str.setAttribute('value', value)
  444. document.body.appendChild(str)
  445. str.select()
  446. document.execCommand('copy')
  447. document.body.removeChild(str)
  448. }
  449. const drawendHandle = (evt) => {
  450. setTimeout(() => {
  451. store.commit('gis/SET_IS_DRAWING', false)
  452. }, 300)
  453. map.removeInteraction(map.getInteractions().getArray().filter(v => v.get(id) === id)[0]);
  454. // 标绘的时候不需要最终结果dom
  455. map.removeOverlay(map.getOverlayById('baseDrawHelpTooltipElementId'))
  456. baseDrawTooltipElement.parentNode.removeChild(baseDrawTooltipElement);
  457. sketch = null; //置空当前绘制的要素对象
  458. baseDrawTooltipElement = null; //置空测量工具提示框对象
  459. baseDrawHelpTooltipElement.parentNode.removeChild(baseDrawHelpTooltipElement);
  460. baseDrawHelpTooltipElement = null; //置空测量工具提示框对象
  461. unByKey(listener);
  462. draw.un('drawstart', drawstartHandle);
  463. draw.un('drawend', drawendHandle);
  464. map.removeInteraction(map.getInteractions().getArray().filter(v => v.get(id) === id)[0]);
  465. map.un('pointermove', pointerMoveHandler)
  466. const baseDrawFeature = evt.feature
  467. getWkt(baseDrawFeature)
  468. }
  469. draw.on('drawend', drawendHandle);
  470. }
  471. addInteraction(); //调用加载绘制交互控件方法,添加绘图进行测量
  472. const pointerMoveHandler = (evt) => {
  473. if (evt.dragging) {
  474. return;
  475. }
  476. let helpMsg = '单击开始标绘';//当前默认提示信息
  477. //判断绘制几何类型设置相应的帮助提示信息
  478. if (sketch) {
  479. const geom = sketch.getGeometry()
  480. helpMsg = continueMsg;
  481. // if (geom.getType() === 'Polygon') {
  482. // helpMsg = continueMsg; //绘制多边形时提示相应内容
  483. // } else if (geom.getType() === 'LineString') {
  484. // helpMsg = continueMsg; //绘制线时提示相应内容
  485. // }
  486. }
  487. baseDrawHelpTooltipElement.innerHTML = helpMsg; //将提示信息设置到对话框中显示
  488. helpTooltip.setPosition(evt.coordinate);//设置帮助提示框的位置
  489. baseDrawHelpTooltipElement.classList.remove('hidden');//移除帮助提示框的隐藏样式进行显示
  490. };
  491. map.on('pointermove', pointerMoveHandler); //地图容器绑定鼠标移动事件,动态显示帮助提示框内容
  492. //地图绑定鼠标移出事件,鼠标移出时为帮助提示框设置隐藏样式
  493. try {
  494. map.getViewport().on('mouseout', () => {
  495. baseDrawHelpTooltipElement.addClass('hidden');
  496. });
  497. } catch (e) {
  498. }
  499. }
  500. }
  501. resolve(() => {
  502. const oldLayer = map.getLayers().getArray().filter(v => v.get(layerFlag[0]) === layerFlag[1])
  503. if (oldLayer) {
  504. oldLayer[0].setStyle(commonStyle(true))
  505. }
  506. // const oldDraw = map.getInteractions().getArray().filter(v => v.get(drawFlag[0]) === drawFlag[1])
  507. // if (oldDraw) {
  508. // oldDraw[0].setStyle(commonStyle(oldDraw[0].get('showText')))
  509. // }
  510. })
  511. }))
  512. }