tool-draw.ts 21 KB

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