olMap.ts 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228
  1. // 引入OpenLayers的主模块
  2. import Map from 'ol/Map';
  3. import View from 'ol/View';
  4. import Feature from 'ol/Feature';
  5. import Point from 'ol/geom/Point';
  6. import VectorLayer from 'ol/layer/Vector';
  7. import VectorSource from 'ol/source/Vector';
  8. import Style from 'ol/style/Style';
  9. import Icon from 'ol/style/Icon';
  10. import Text from 'ol/style/Text';
  11. import Projection from 'ol/proj/Projection';
  12. import { getWidth, getTopLeft } from 'ol/extent';
  13. import TileLayer from 'ol/layer/Tile';
  14. import WMTS from 'ol/source/WMTS';
  15. import WMTSTileGrid from 'ol/tilegrid/WMTS';
  16. import WMTSCapabilities from 'ol/format/WMTSCapabilities';
  17. import { Fill, Stroke } from 'ol/style';
  18. import proj4 from 'proj4';
  19. import { register } from 'ol/proj/proj4';
  20. import { defaults } from 'ol/control';
  21. import Vector from 'ol/layer/Vector';
  22. import SourceVector from 'ol/source/Vector';
  23. import GeoJSON from 'ol/format/GeoJSON';
  24. import { fromLonLat } from 'ol/proj';
  25. import axios from 'axios';
  26. import { fromExtent } from 'ol/geom/Polygon';
  27. import { LinearRing, LineString, Polygon } from 'ol/geom';
  28. import { Graticule } from 'ol/layer';
  29. import { getPointsCenter, mergeGeoJsonPolygons } from '@/utils/gisUtils';
  30. import { Cluster } from 'ol/source';
  31. import CircleStyle from 'ol/style/Circle';
  32. import Overlay from 'ol/Overlay';
  33. import { DoubleClickZoom, DragPan, Draw, Select } from 'ol/interaction';
  34. import { click } from 'ol/events/condition';
  35. import Circle from 'ol/geom/Circle';
  36. import { deepClone, getRgba, hexToRgba, initDrag, rgbToRgba } from '@/utils';
  37. import { createBox } from 'ol/interaction/Draw';
  38. import * as turf from '@turf/turf';
  39. import { nanoid } from 'nanoid';
  40. import carImg from '@/assets/images/car.png';
  41. const tk = 'a8df87f1695d224d2679aa805c1268d9';
  42. const commonUrl = import.meta.env.VITE_APP_BASE_API2 + 'api/oneShare/proxyHandler/gd/';
  43. // proj4.defs('EPSG:4490', '+proj=longlat +ellps=GRS80 +no_defs +type=crs');
  44. proj4.defs(
  45. 'EPSG:4490',
  46. 'GEOGCS["China Geodetic Coordinate System 2000",DATUM["China_2000",SPHEROID["CGCS2000",6378137,298.257222101,AUTHORITY["EPSG","1024"]],AUTHORITY["EPSG","1043"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4490"]]'
  47. );
  48. // proj4.defs('EPSG:4525', '+proj=tmerc +lat_0=0 +lon_0=111 +k=1 +x_0=37500000 +y_0=0 +ellps=GRS80 +units=m +no_defs +type=crs');
  49. register(proj4);
  50. const projection = new Projection({
  51. code: 'EPSG:4490',
  52. units: 'degrees',
  53. axisOrientation: 'neu'
  54. });
  55. projection.setExtent([-180, -90, 180, 90]);
  56. projection.setWorldExtent([-180, -90, 180, 90]);
  57. const projectionExtent = [-180, -90, 180, 90];
  58. const size = getWidth(projectionExtent) / 256;
  59. const resolutions = [];
  60. for (let z = 2; z < 22; ++z) {
  61. resolutions[z] = size / Math.pow(2, z);
  62. }
  63. export class olMap {
  64. private map;
  65. private options;
  66. private markers = [];
  67. private drawOptions = {
  68. graphicsType: 'circle',
  69. strokeColor: '#f80102',
  70. strokeOpacity: 1,
  71. strokeWeight: 2,
  72. fillColor: '#f80102',
  73. fillOpacity: 0,
  74. strokeStyle: 'solid',
  75. icon: '',
  76. iconName: ''
  77. };
  78. private plot;
  79. private drawVector;
  80. private drawTool;
  81. private vectorLayer;
  82. // 边界遮罩图层
  83. private maskLayer;
  84. private maskLayer2;
  85. // 显示信息框
  86. private infoWindow;
  87. private select;
  88. // 车辆轨迹
  89. private carLayer;
  90. private carFeature;
  91. private traceFeature;
  92. // 自定义绘制结束调用方法
  93. private drawEndMethod;
  94. private selectedFeature;
  95. constructor(options) {
  96. this.options = options;
  97. this.map = new Map({
  98. controls: defaults({
  99. zoom: false,
  100. rotate: false
  101. }),
  102. layers: [],
  103. target: options.dom,
  104. view: new View({
  105. center: options.center && options.center.length === 2 ? [options.center[0], options.center[1]] : [110.90153121597234, 21.98323671981171],
  106. zoom: options.zoom ? options.zoom : 9.6,
  107. projection: projection,
  108. maxZoom: options.maxZoom ? options.maxZoom : 18,
  109. minZoom: options.minZoom ? options.minZoom : 1
  110. })
  111. });
  112. // 初始化比例尺
  113. if (options.showScale) {
  114. // this.map.addControl(new AMap.Scale());
  115. }
  116. if (options.drawTool?.use) {
  117. this.initMouseTool(options.drawTool);
  118. }
  119. this.initLayer(options);
  120. // 给标注点添加手势
  121. this.map.on('pointermove', (e) => {
  122. this.map.getTargetElement().style.cursor = 'auto';
  123. const features = this.map.getFeaturesAtPixel(e.pixel);
  124. features.forEach((feature) => {
  125. const originalFeature = feature.get('features') ? feature.get('features')[0] : '';
  126. if (!!originalFeature && originalFeature.get('pointer')) {
  127. this.map.getTargetElement().style.cursor = 'pointer'; //设置鼠标样式
  128. } else {
  129. this.map.getTargetElement().style.cursor = 'auto';
  130. }
  131. });
  132. });
  133. // 创建选择交互
  134. this.select = new Select({
  135. condition: click,
  136. style: null,
  137. filter: (feature, layer) => {
  138. // 只有vectorLayer图层可选择
  139. return layer === this.vectorLayer;
  140. }
  141. });
  142. this.map.addInteraction(this.select);
  143. // 监听Select交互的select事件
  144. this.select.on('select', (event) => {
  145. const selectedFeatures = event.selected[0]; // 获取被选中的要素集合
  146. const features = selectedFeatures.get('features');
  147. if (selectedFeatures && !!features) {
  148. const originalFeature = features[0];
  149. const size = features.length;
  150. if (size === 1) {
  151. if (this.selectedFeature !== originalFeature) {
  152. if (this.selectedFeature) {
  153. this.selectedFeature.set('icon', this.selectedFeature.get('image'));
  154. }
  155. this.selectedFeature = originalFeature;
  156. const icon = originalFeature.get('imageHover');
  157. const extData = originalFeature.get('extData');
  158. originalFeature.set('icon', icon);
  159. options.onMarkerClick(extData);
  160. }
  161. } else {
  162. // 聚合要素
  163. this.select.getFeatures().clear();
  164. const currentZoom = this.map.getView().getZoom();
  165. this.map.getView().setZoom(currentZoom + 1);
  166. const points = [];
  167. features.forEach((feature) => {
  168. const geometry = feature.getGeometry(); // 获取要素的几何对象
  169. const type = geometry.getType(); // 获取几何类型
  170. if (type === 'Point') {
  171. points.push(geometry.getCoordinates());
  172. }
  173. });
  174. const newFeature = getPointsCenter(points);
  175. this.map.getView().setCenter(newFeature.geometry.coordinates);
  176. event.selected = [];
  177. }
  178. }
  179. });
  180. }
  181. async initLayer(options) {
  182. // 添加新的图层
  183. if (Array.isArray(options.id)) {
  184. for (const layer of options.id) {
  185. if (typeof layer === 'string') {
  186. await this.formatXml(layer); // 等待当前 layer 处理完成
  187. } else {
  188. await this.formatXml(layer.id, layer.minZoom, layer.maxZoom, layer.zIndex, layer.visible); // 等待当前 layer 处理完成
  189. }
  190. }
  191. } else if (options.id === 'tianditu') {
  192. await this.formatXml2();
  193. } else if (options.id) {
  194. // 如果 options.id 不是数组,但确实是一个图层,则直接处理
  195. await this.formatXml(options.id, options.minZoom, options.maxZoom, options.zIndex, options.visible);
  196. }
  197. // 创建Vector层并添加到地图上
  198. this.vectorLayer = new VectorLayer({
  199. source: new VectorSource({
  200. features: []
  201. })
  202. });
  203. this.map.addLayer(this.vectorLayer);
  204. if (typeof this.options.onLoadCompleted === 'function') {
  205. this.options.onLoadCompleted(this.map);
  206. }
  207. }
  208. formatXml2() {
  209. const baseUrl = 'https://t{0-7}.tianditu.gov.cn/';
  210. const vecType = 'vec'; // 矢量图层类型
  211. const projType = 'w'; // 投影类型:web mercator
  212. const reqParams = {
  213. SERVICE: 'WMTS',
  214. REQUEST: 'GetTile',
  215. VERSION: '1.0.0',
  216. LAYER: '',
  217. STYLE: 'default',
  218. TILEMATRIXSET: projType,
  219. FORMAT: 'tiles',
  220. TILECOL: '{x}',
  221. TILEROW: '{y}',
  222. TILEMATRIX: '{z}',
  223. tk: key
  224. };
  225. const newParams = Object.assign({}, params, { LAYER: dataType });
  226. let paramsStr = '';
  227. for (const [k, v] of Object.entries(newParams)) {
  228. paramsStr += `${k}=${v}&`;
  229. }
  230. return `${baseUrl}${dataType}_${projType}/wmts?${paramsStr.slice(0, -1)}`;
  231. }
  232. formatXml(code: string, minZoom?: number, maxZoom?: number, zIndex?: number, visible?: boolean) {
  233. const xml = new WMTSCapabilities();
  234. return this.getCapabilities(code).then((lists) => {
  235. const geojson = xml.read(lists.data);
  236. const data = geojson.Contents.Layer[0];
  237. const layerParam = {
  238. layerName: data.Abstract,
  239. styleName: data.Identifier,
  240. tilematrixset: data.TileMatrixSetLink[0].TileMatrixSet,
  241. format: data.Format[0]
  242. };
  243. this.createWmsLayer(code, layerParam, minZoom, maxZoom, zIndex, visible);
  244. });
  245. }
  246. // 请求接口获取地图信息
  247. getCapabilities(code) {
  248. return axios.get(commonUrl + code + '?SERVICE=WMTS&REQUEST=GetCapabilities');
  249. }
  250. // 请求地图图片加载图层
  251. createWmsLayer(code, layerParam, minZoom = 0, maxZoom, zIndex = -1, visible = true) {
  252. const source = new WMTS({
  253. url: commonUrl + code,
  254. crossOrigin: 'Anonymous',
  255. layer: layerParam.layerName,
  256. style: layerParam.styleName,
  257. matrixSet: layerParam.tilematrixset,
  258. format: layerParam.format,
  259. wrapX: true,
  260. tileGrid: new WMTSTileGrid({
  261. origin: getTopLeft(projectionExtent),
  262. resolutions: resolutions,
  263. matrixIds: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21']
  264. })
  265. });
  266. const layer = new TileLayer({
  267. name: code,
  268. source: source,
  269. zIndex: zIndex,
  270. minZoom: minZoom,
  271. maxZoom,
  272. visible: visible
  273. });
  274. layer.set('layerName', code);
  275. this.map.addLayer(layer);
  276. }
  277. // 初始化绘画工具
  278. initMouseTool(options) {
  279. this.drawOptions = {
  280. graphicsType: options.graphicsType ? options.graphicsType : 'cirlce',
  281. strokeColor: options.color,
  282. strokeOpacity: 1,
  283. strokeWeight: 2,
  284. fillColor: options.color,
  285. fillOpacity: options.drawType === '1' ? 0 : 0.5,
  286. strokeStyle: 'solid'
  287. };
  288. // 创建矢量图层用于绘制
  289. this.drawVector = new Vector({
  290. source: new VectorSource({
  291. features: []
  292. })
  293. });
  294. this.map.addLayer(this.drawVector);
  295. }
  296. // 绘制结束事件
  297. onDrawEnd(event) {
  298. const feature = event.feature;
  299. const aa = feature.getGeometry();
  300. // 继续编辑编辑
  301. this.drawGraphics(this.drawOptions.graphicsType);
  302. }
  303. // 绘制图形
  304. drawGraphics(newOptions: MouseTool) {
  305. const typeList = {
  306. circle: 'Circle',
  307. rectangle: 'Circle',
  308. polygon: 'Polygon',
  309. measureArea: 'Polygon',
  310. straightLine: 'LineString',
  311. marker: 'Point',
  312. text: 'Point',
  313. anyLine: 'AnyLine'
  314. };
  315. const data = getRgba(newOptions.color);
  316. let geometryFunction = null;
  317. if (newOptions.graphicsType === 'rectangle') {
  318. // 绘制矩形的方法
  319. geometryFunction = createBox();
  320. }
  321. if (!!typeList[newOptions.graphicsType]) {
  322. if (newOptions.graphicsType === 'text') {
  323. this.drawOptions = {
  324. type: newOptions.graphicsType,
  325. title: newOptions.title,
  326. text: newOptions.text,
  327. fontSize: newOptions.fontSize,
  328. fontColor: newOptions.fontColor,
  329. lnglat: newOptions.lnglat
  330. };
  331. // 绘制文字
  332. return this.addText(this.drawOptions);
  333. } else {
  334. this.drawOptions = {
  335. type: newOptions.graphicsType,
  336. title: newOptions.title,
  337. strokeColor: !!data.color ? data.color : newOptions.color,
  338. strokeOpacity: 1,
  339. strokeWeight: 1,
  340. fillColor: data.color,
  341. fillOpacity: data.opacity,
  342. strokeStyle: 'solid'
  343. };
  344. if (newOptions.graphicsType === 'marker') {
  345. this.drawOptions.icon = newOptions.icon;
  346. this.drawOptions.iconName = newOptions.iconName;
  347. this.drawOptions.size = newOptions.size;
  348. }
  349. this.closeDraw();
  350. if (newOptions.graphicsType === 'anyLine') {
  351. this.drawAnyLine();
  352. } else {
  353. // 创建绘制交互
  354. let style = new Style({
  355. stroke: new Stroke({
  356. color: rgbToRgba(this.drawOptions.strokeColor, this.drawOptions.strokeOpacity),
  357. width: this.drawOptions.strokeWeight
  358. }),
  359. fill: new Fill({
  360. color: rgbToRgba(this.drawOptions.fillColor, this.drawOptions.fillOpacity)
  361. })
  362. });
  363. this.drawTool = new Draw({
  364. source: this.drawVector.getSource(),
  365. type: typeList[newOptions.graphicsType],
  366. geometryFunction: geometryFunction,
  367. style: style
  368. });
  369. // 添加绘制交互到地图
  370. this.map.addInteraction(this.drawTool);
  371. // 监听绘制结束事件
  372. this.drawTool.on('drawend', (event) => {
  373. const feature = event.feature;
  374. // 获取几何对象
  375. if (newOptions.graphicsType !== 'marker') {
  376. if (newOptions.graphicsType === 'measureArea') {
  377. const geometry = feature.getGeometry();
  378. const coordinates = geometry.getCoordinates();
  379. const pathArr = coordinates[0];
  380. const area = turf.area(turf.polygon([pathArr]));
  381. style = new Style({
  382. stroke: style.getStroke(),
  383. fill: style.getFill(),
  384. text: new Text({
  385. text: '区域面积' + area.toFixed(2) + '平方米',
  386. font: '14px Calibri,sans-serif',
  387. fill: new Fill({ color: '#000' }),
  388. stroke: new Stroke({
  389. color: '#fff',
  390. width: 3
  391. }),
  392. overflow: true
  393. })
  394. });
  395. }
  396. feature.setStyle(style);
  397. }
  398. });
  399. }
  400. }
  401. }
  402. return this.drawOptions;
  403. }
  404. // 空间分析绘制图形
  405. drawGraphics2(newOptions: MouseTool) {
  406. const typeList = {
  407. circle: 'Circle',
  408. rectangle: 'Circle',
  409. polygon: 'Polygon'
  410. };
  411. let geometryFunction = null;
  412. if (newOptions.graphicsType === 'rectangle') {
  413. // 绘制矩形的方法
  414. geometryFunction = createBox();
  415. }
  416. if (!!typeList[newOptions.graphicsType]) {
  417. this.drawOptions = {
  418. type: newOptions.graphicsType,
  419. strokeColor: newOptions.color,
  420. strokeOpacity: 1,
  421. strokeWeight: 1,
  422. fillColor: newOptions.color,
  423. fillOpacity: newOptions.drawType === '1' ? '0' : '0.5',
  424. strokeStyle: 'solid'
  425. };
  426. this.closeDraw();
  427. // 创建绘制交互
  428. const style = new Style({
  429. stroke: new Stroke({
  430. color: hexToRgba(this.drawOptions.strokeColor, this.drawOptions.strokeOpacity),
  431. width: this.drawOptions.strokeWeight
  432. }),
  433. fill: new Fill({
  434. color: hexToRgba(this.drawOptions.fillColor, this.drawOptions.fillOpacity)
  435. })
  436. });
  437. this.drawTool = new Draw({
  438. source: this.drawVector.getSource(),
  439. type: typeList[newOptions.graphicsType],
  440. geometryFunction: geometryFunction,
  441. style: style
  442. });
  443. // 添加绘制交互到地图
  444. this.map.addInteraction(this.drawTool);
  445. // 监听绘制结束事件
  446. this.drawTool.on('drawend', (event) => {
  447. const feature = event.feature;
  448. // 获取几何对象
  449. feature.setStyle(style);
  450. });
  451. }
  452. }
  453. addText(options) {
  454. // 创建文本覆盖物
  455. const style = new Style({
  456. text: new Text({
  457. text: options.text,
  458. font: options.fontSize + ' Calibri,sans-serif',
  459. fill: new Fill({ color: options.fontColor }),
  460. // stroke: new Stroke({
  461. // color: '#fff',
  462. // width: 3
  463. // }),
  464. overflow: true
  465. })
  466. });
  467. const text = new Feature({
  468. geometry: new Point(options.lnglat)
  469. });
  470. text.setStyle(style);
  471. // 将文本覆盖物添加到地图
  472. this.drawVector.getSource().addFeature(text);
  473. const id = nanoid();
  474. text.set('id', id);
  475. const data: any = deepClone(options);
  476. data.id = id;
  477. return { text, data };
  478. }
  479. drawAnyLine() {
  480. document.addEventListener('touchend', this.handleTouchEnd);
  481. this.map.on('pointerdown', this.handleTouchStart);
  482. this.map.on('pointermove', this.handleTouchMove.bind(null, this.options));
  483. document.addEventListener('mouseup', this.handleTouchEnd.bind(null, this.options));
  484. }
  485. handleTouchStart(e) {
  486. this.drawing = true;
  487. const interactions = this.map.getInteractions();
  488. interactions.forEach((interaction) => {
  489. if (interaction instanceof DragPan || interaction instanceof DoubleClickZoom) {
  490. interaction.setActive(false); // 修改为需要的值,true或false
  491. }
  492. });
  493. this.path = [e.lnglat];
  494. // if (this.anyLine) {
  495. // map.remove(anyLine);
  496. // }
  497. }
  498. handleTouchMove(options, e) {
  499. if (!this.drawing) return;
  500. this.path.push(e.lnglat);
  501. // if (anyLine) {
  502. // map.remove(anyLine);
  503. // }
  504. this.anyLine = new Feature({
  505. geometry: new LineString(this.path) // path为坐标数组,如[[x1,y1], [x2,y2]]
  506. });
  507. const lineStyle = new Style({
  508. stroke: new Stroke({
  509. color: hexToRgba(options.strokeColor, options.strokeOpacity), // 合并颜色与透明度
  510. width: options.strokeWeight,
  511. lineJoin: 'round' // 线段连接处圆角
  512. })
  513. });
  514. this.anyLine.setStyle(lineStyle);
  515. // new AMap.Polyline({
  516. // path: path,
  517. // strokeColor: options.strokeColor,
  518. // strokeOpacity: options.strokeOpacity,
  519. // strokeWeight: options.strokeWeight,
  520. // strokeStyle: options.strokeStyle,
  521. // lineJoin: 'round'
  522. // });
  523. this.drawVector.getSource().addFeature(this.anyLine);
  524. }
  525. handleTouchEnd(options) {
  526. // drawing = false;
  527. // map.setStatus({
  528. // showIndoorMap: true,
  529. // dragEnable: true,
  530. // keyboardEnable: true,
  531. // doubleClickZoom: true,
  532. // zoomEnable: true,
  533. // rotateEnable: true
  534. // });
  535. // map.off('touchstart', handleTouchStart);
  536. // map.on('touchmove', handleTouchMove.bind(null, options));
  537. // document.addEventListener('touchend', handleTouchEnd.bind(null, options));
  538. // map.off('mousedown', handleTouchStart);
  539. // map.off('mousemove', handleTouchMove);
  540. // document.removeEventListener('mouseup', handleTouchEnd);
  541. // if (!!drawEndMethod) {
  542. // drawEndMethod(options, anyLine);
  543. // }
  544. // anyLine = null;
  545. }
  546. // 关闭绘制
  547. closeDraw() {
  548. if (!this.drawTool) return;
  549. this.map.removeInteraction(this.drawTool);
  550. // this.drawTool.abortDrawing();
  551. }
  552. // 切换图层
  553. async replaceLayers(newLayers, loadendFunc) {
  554. // 遍历当前的所有图层并移除它们
  555. this.map.getLayers().forEach((layer) => {
  556. this.map.removeLayer(layer);
  557. });
  558. if (Array.isArray(newLayers)) {
  559. for (const layer of newLayers) {
  560. if (typeof layer === 'string') {
  561. await this.formatXml(layer); // 等待当前 layer 处理完成
  562. } else {
  563. await this.formatXml(layer.id, layer.minZoom, layer.maxZoom, layer.zIndex, layer.visible); // 等待当前 layer 处理完成
  564. }
  565. }
  566. } else {
  567. // 如果 options.id 不是数组,但确实是一个图层,则直接处理
  568. await this.formatXml(newLayers.id, newLayers.minZoom, newLayers.maxZoom, newLayers.zIndex, newLayers.visible);
  569. }
  570. // 创建Vector层并添加到地图上
  571. this.vectorLayer = new VectorLayer({
  572. source: new VectorSource({
  573. features: []
  574. })
  575. });
  576. this.map.addLayer(this.vectorLayer);
  577. const point = JSON.parse(JSON.stringify(this.markers));
  578. this.markers = [];
  579. this.addMarker(point);
  580. if (loadendFunc) {
  581. loadendFunc();
  582. }
  583. }
  584. // 集群点样式
  585. clusterStyle(feature) {
  586. const originalFeature = feature.get('features')[0];
  587. const size = feature.get('features').length;
  588. if (size > 1) {
  589. const outerCircle = new CircleStyle({
  590. radius: 20,
  591. fill: new Fill({
  592. color: 'rgba(79, 176, 206, 0.5)'
  593. }),
  594. stroke: new Stroke({
  595. color: 'rgba(79, 176, 206, 1)'
  596. })
  597. });
  598. return [
  599. new Style({
  600. image: outerCircle,
  601. text: new Text({
  602. text: size.toString(),
  603. font: '14px sans-serif',
  604. fill: new Fill({
  605. color: '#ffff'
  606. })
  607. })
  608. })
  609. ];
  610. }
  611. return new Style({
  612. geometry: originalFeature.getGeometry(),
  613. image: new Icon({
  614. anchor: [0.5, 0.5],
  615. scale: 0.146,
  616. anchorXUnits: 'fraction',
  617. anchorYUnits: 'pixels',
  618. src: originalFeature.get('icon')
  619. })
  620. });
  621. }
  622. addMarker(points) {
  623. this.clearMarker();
  624. const features = [];
  625. points.forEach((point) => {
  626. // 创建标注点
  627. const feature = new Feature({
  628. // 必须是数字类型,字符串不识别
  629. geometry: new Point([Number(point.longitude), Number(point.latitude)]),
  630. name: point.name,
  631. icon: point.icon,
  632. imageHover: point.imageHover,
  633. size: point.size,
  634. pointer: true,
  635. extData: point
  636. });
  637. // 设置自定义属性
  638. feature.set('pointer', true);
  639. const img = new Image();
  640. img.onload = () => {
  641. // 图片加载完成后,可以访问其 width 和 height 属性
  642. const width = img.width;
  643. const height = img.height;
  644. const style = new Style({
  645. image: new Icon({
  646. anchor: [0.5, 0.5],
  647. anchorXUnits: 'fraction',
  648. anchorYUnits: 'pixels',
  649. src: point.icon,
  650. size: [width, height],
  651. scale: !!point.size[0] ? point.size[0] / width : 1
  652. }),
  653. text: new Text({
  654. text: point.name,
  655. fill: new Fill({
  656. color: '#000'
  657. }),
  658. stroke: new Stroke({
  659. color: '#fff',
  660. width: 3
  661. })
  662. })
  663. });
  664. features.setStyle(style);
  665. };
  666. img.src = point.icon; // 设置图片的 URL,触发加载
  667. features.push(feature);
  668. this.markers.push(point);
  669. });
  670. const vectorSource = new VectorSource({
  671. features: features
  672. });
  673. const clusterSource = new Cluster({
  674. distance: 30,
  675. source: vectorSource
  676. });
  677. this.vectorLayer.setStyle(this.clusterStyle);
  678. this.vectorLayer.setSource(clusterSource);
  679. }
  680. // 清除所有标加
  681. clearMarker() {
  682. if (!this.vectorLayer) return;
  683. this.vectorLayer.getSource().clear();
  684. }
  685. showInfo(content, position, isCustom) {
  686. this.hideInfo();
  687. if (!this.infoWindow) {
  688. this.infoWindow = new Overlay({
  689. element: content,
  690. positioning: 'bottom-center', // 你可以根据需要调整定位方式
  691. offset: [0, -10] // 偏移量,用于调整覆盖层相对于要素的位置
  692. });
  693. }
  694. this.infoWindow.setPosition(position);
  695. initDrag(this.infoWindow.element);
  696. this.map.addOverlay(this.infoWindow);
  697. }
  698. hideInfo(flag) {
  699. this.map.removeOverlay(this.infoWindow);
  700. this.infoWindow = null;
  701. if (!!flag && this.select) {
  702. this.select.getFeatures().clear();
  703. }
  704. }
  705. /**
  706. *
  707. * @param {Geojon} chaozhou 根据geojson对象创建Featrue对象
  708. * @returns VectorLayer
  709. */
  710. createVecByJson(json, options) {
  711. const format = new GeoJSON();
  712. const fs = format.readFeatures(json);
  713. this.maskLayer = new VectorLayer({
  714. source: new VectorSource(),
  715. style: new Style({
  716. fill: new Fill({
  717. color: options.fillColor ? options.fillColor : 'rgba(16, 36, 59, 0.65)'
  718. }),
  719. stroke: new Stroke({
  720. color: options.strokeColor ? options.strokeColor : 'rgba(38, 138, 185, 1)',
  721. width: 2
  722. })
  723. }),
  724. zIndex: options.zIndex ? options.zIndex : 99
  725. });
  726. this.map.addLayer(this.maskLayer);
  727. const extent = [-180, -90, 180, 90];
  728. const polygonRing = fromExtent(extent);
  729. fs.forEach((x) => {
  730. const ft = x.values_.geometry;
  731. const coords = ft.getCoordinates();
  732. coords.forEach((coord) => {
  733. const linearRing = new LinearRing(coord[0]);
  734. polygonRing.appendLinearRing(linearRing);
  735. });
  736. });
  737. const convertFt = new Feature({
  738. geometry: polygonRing
  739. });
  740. this.maskLayer.getSource().addFeature(convertFt);
  741. }
  742. createVecByJson2(json, options) {
  743. if (!!this.maskLayer2) {
  744. // this.map.addLayer(this.maskLayer);
  745. this.map.addLayer(this.maskLayer2);
  746. } else {
  747. this.maskLayer2 = new VectorLayer({
  748. source: new VectorSource(),
  749. style: new Style({
  750. fill: new Fill({
  751. color: 'rgba(0, 0, 0, 0)'
  752. }),
  753. stroke: new Stroke({
  754. color: options.strokeColor ? options.strokeColor : '#268ab9',
  755. width: options.strokeWeight ? options.strokeWeight : 1
  756. })
  757. })
  758. });
  759. this.map.addLayer(this.maskLayer2);
  760. this.maskLayer = new VectorLayer({
  761. source: new VectorSource(),
  762. style: new Style({
  763. fill: new Fill({
  764. color: options.fillColor ? options.fillColor : 'rgba(16, 36, 59, 0.65)'
  765. }),
  766. stroke: new Stroke({
  767. color: options.strokeColor ? options.strokeColor : 'rgba(38, 138, 185, 1)',
  768. width: 2
  769. })
  770. }),
  771. zIndex: options.zIndex ? options.zIndex : 99
  772. });
  773. // // 合并区边界
  774. // const format = new GeoJSON();
  775. // const data2 = mergeGeoJsonPolygons(json);
  776. // const fs = format.readFeatures(data2);
  777. // const extent = [-180, -90, 180, 90];
  778. // const polygonRing = fromExtent(extent);
  779. // fs.forEach((x) => {
  780. // const ft = x.values_.geometry;
  781. // const coords = ft.getCoordinates();
  782. // coords.forEach((coord) => {
  783. // const linearRing = new LinearRing(coord[0]);
  784. // polygonRing.appendLinearRing(linearRing);
  785. // });
  786. // });
  787. // const convertFt = new Feature({
  788. // geometry: polygonRing
  789. // });
  790. // this.maskLayer.getSource().addFeature(convertFt);
  791. // this.map.addLayer(this.maskLayer);
  792. // 边界部分
  793. json.features.forEach((feature) => {
  794. if (feature.geometry.type === 'Polygon') {
  795. const feature2 = new Feature({
  796. geometry: new LineString(feature.geometry.coordinates[0])
  797. });
  798. this.maskLayer2.getSource().addFeature(feature2);
  799. } else if (feature.geometry.type === 'MultiPolygon') {
  800. feature.geometry.coordinates.forEach((polygonCoords) => {
  801. const feature2 = new Feature({
  802. geometry: new LineString(polygonCoords[0])
  803. });
  804. this.maskLayer2.getSource().addFeature(feature2);
  805. });
  806. }
  807. });
  808. }
  809. }
  810. /**
  811. * @description 创建矢量图层
  812. * @param {String} layerName 图层名称
  813. * @param {Number} zIndex 地图层级默认是0
  814. * @returns
  815. */
  816. createVecLayer(layerName = '', zIndex = 0) {
  817. const source = new SourceVector({
  818. crossOrigin: 'anonymous'
  819. });
  820. const layer = new Vector({
  821. source,
  822. zIndex
  823. });
  824. layer.set('layerName', layerName);
  825. return layer;
  826. }
  827. // 分布图遮罩层
  828. createMask(data) {
  829. this.removeMask();
  830. if (!data || data.length === 0) return;
  831. data.forEach((item) => {
  832. if (!item.points || item.points.length === 0) return;
  833. // 遮罩图层的样式
  834. const maskStyle = new Style({
  835. fill: new Fill({
  836. color: item.color // 红色遮罩,50%透明度
  837. }),
  838. stroke: new Stroke({
  839. color: 'rgba(159,159,159,0.7)',
  840. width: 1
  841. })
  842. });
  843. // 遮罩图层的矢量数据源(初始为空)
  844. const maskSource = new VectorSource();
  845. // 创建一个多边形特征
  846. const polygonFeature = new Feature({
  847. geometry: new Polygon(item.points)
  848. });
  849. const maskLayer = new VectorLayer({
  850. source: maskSource,
  851. style: maskStyle,
  852. properties: {
  853. name: 'mask'
  854. }
  855. });
  856. this.map.addLayer(maskLayer);
  857. // 将多边形特征添加到遮罩数据源中
  858. maskSource.addFeature(polygonFeature);
  859. });
  860. }
  861. removeMask() {
  862. //移除图层
  863. const layersArray = this.map.getLayers().getArray();
  864. layersArray.forEach((layer) => {
  865. // 检查图层是否有自定义属性,并且该属性是否匹配你要移除的图层的标识符
  866. if (layer.get('name') === 'mask') {
  867. this.map.removeLayer(layer);
  868. }
  869. });
  870. }
  871. removeMask2() {
  872. if (this.maskLayer) {
  873. this.map.removeLayer(this.maskLayer);
  874. this.maskLayer = null;
  875. }
  876. }
  877. removeMask3(isHide) {
  878. if (this.maskLayer) {
  879. this.map.removeLayer(this.maskLayer);
  880. if (!isHide) {
  881. this.maskLayer = [];
  882. }
  883. }
  884. if (this.maskLayer2) {
  885. this.map.removeLayer(this.maskLayer2);
  886. if (!isHide) {
  887. this.maskLayer2 = [];
  888. }
  889. }
  890. }
  891. /**
  892. * 绘制经纬线
  893. * visible: boolean 是否可见
  894. */
  895. handleLngLatLine(visible: boolean) {
  896. // 创建经纬网图层
  897. const graticule = new Graticule({
  898. name: 'Graticule',
  899. showLabels: true, // 为每条刻度线绘制一个带有各自纬度/经度的标签
  900. wrapX: false, // 是否水平重复经纬网
  901. targetSize: 230,
  902. zIndex: 99999,
  903. strokeStyle: new Stroke({
  904. // 用于绘制刻度线的样式
  905. color: '#dcdcdc', // 线条颜色
  906. width: 1 // 线条宽度
  907. }),
  908. lonLabelStyle: new Text({
  909. font: '12px Calibri,sans-serif',
  910. textBaseline: 'bottom',
  911. fill: new Fill({
  912. color: '#9d9d9d'
  913. })
  914. }),
  915. latLabelPosition: 0,
  916. latLabelStyle: new Text({
  917. font: '12px Calibri,sans-serif',
  918. textAlign: 'left',
  919. textBaseline: 'end',
  920. fill: new Fill({
  921. color: '#9d9d9d'
  922. })
  923. })
  924. });
  925. if (visible) {
  926. this.map.addLayer(graticule);
  927. } else {
  928. this.removeLayer('Graticule');
  929. }
  930. }
  931. /**
  932. * 移除指定name的layer
  933. * layerName: string
  934. * */
  935. removeLayer(layerName: string) {
  936. const layers = this.map.getLayers();
  937. layers.forEach((element) => {
  938. if (!!element && element.get('name') === layerName) {
  939. //移除
  940. this.map.removeLayer(element);
  941. }
  942. });
  943. }
  944. // 创建图形
  945. createGraphics(data: any) {
  946. if (data.type === 'circle') {
  947. // 绘制圆形
  948. return this.createCircle(data);
  949. } else if (['polygon', 'rectangle'].includes(data.type)) {
  950. // 绘制矩形、多边形
  951. return this.createPolygon(data);
  952. }
  953. // else if (data.type === 'marker') {
  954. // // 绘制图标
  955. // return createMarker(data);
  956. // } else if (data.type === 'measureArea') {
  957. // // 绘制面积
  958. // return createMeasureArea(data);
  959. // } else if (data.type === 'text') {
  960. // // 文字
  961. // return addText(data);
  962. // } else if (data.type === 'straightLine') {
  963. // // 直线
  964. // return createStraightLine(data);
  965. // }
  966. }
  967. createCircle(data) {
  968. const circle = new Circle(data.center, data.radius);
  969. const feature = new Feature(circle);
  970. feature.setStyle(
  971. new Style({
  972. stroke: new Stroke({
  973. color: rgbToRgba(data.strokeColor, data.strokeOpacity),
  974. width: data.strokeWeight
  975. }),
  976. fill: new Fill({
  977. color: rgbToRgba(data.fillColor, data.fillOpacity)
  978. })
  979. })
  980. );
  981. this.drawVector.getSource().addFeature(feature);
  982. return feature;
  983. }
  984. createPolygon(data) {
  985. const polygon = new Polygon([data.path]);
  986. const feature = new Feature(polygon);
  987. feature.setStyle(
  988. new Style({
  989. stroke: new Stroke({
  990. color: rgbToRgba(data.strokeColor, data.strokeOpacity),
  991. width: data.strokeWeight
  992. }),
  993. fill: new Fill({
  994. color: rgbToRgba(data.fillColor, data.fillOpacity)
  995. })
  996. })
  997. );
  998. this.drawVector.getSource().addFeature(feature);
  999. return feature;
  1000. }
  1001. createLineString(data) {
  1002. const lineString = new LineString(data.path);
  1003. const feature = new Feature(lineString);
  1004. feature.setStyle(
  1005. new Style({
  1006. stroke: new Stroke({
  1007. color: rgbToRgba(data.strokeColor, data.strokeOpacity),
  1008. width: data.strokeWeight
  1009. }),
  1010. fill: new Fill({
  1011. color: rgbToRgba(data.fillColor, data.fillOpacity)
  1012. })
  1013. })
  1014. );
  1015. this.drawVector.getSource().addFeature(feature);
  1016. return feature;
  1017. }
  1018. trackPlayback(lineArr) {
  1019. if (!!this.carFeature) {
  1020. this.carLayer.getSource().removeFeature(this.carFeature);
  1021. }
  1022. if (!!this.traceFeature) {
  1023. this.carLayer.getSource().removeFeature(this.traceFeature);
  1024. }
  1025. const getAngle = (point1, point2) => {
  1026. let arc = 0;
  1027. if (point2 && point2.length && point1 && point1.length) {
  1028. if ((point2[0] - point1[0] >= 0 && point2[1] - point1[1] >= 0) || (point2[0] - point1[0] < 0 && point2[1] - point1[1] > 0)) {
  1029. arc = Math.atan((point2[0] - point1[0]) / (point2[1] - point1[1]));
  1030. } else if ((point2[0] - point1[0] > 0 && point2[1] - point1[1] < 0) || (point2[0] - point1[0] < 0 && point2[1] - point1[1] < 0)) {
  1031. arc = Math.PI + Math.atan((point2[0] - point1[0]) / (point2[1] - point1[1]));
  1032. }
  1033. }
  1034. return arc;
  1035. };
  1036. let lastTime = Date.now();
  1037. const source = new VectorSource();
  1038. let distance = 0;
  1039. const angle = getAngle(lineArr[0], lineArr[1]);
  1040. const speed = 500;
  1041. let animationFlag = false;
  1042. this.carFeature = new Feature({
  1043. geometry: new Point(lineArr[0])
  1044. });
  1045. const icon = new Icon({
  1046. crossOrigin: 'anonymous',
  1047. src: carImg,
  1048. width: 13,
  1049. height: 26,
  1050. anchor: [0.5, 0.5],
  1051. rotation: angle
  1052. });
  1053. this.carFeature.setStyle(
  1054. new Style({
  1055. image: icon
  1056. })
  1057. );
  1058. this.traceFeature = new Feature({
  1059. geometry: new LineString(lineArr)
  1060. });
  1061. let route = new LineString(lineArr);
  1062. this.carLayer = new VectorLayer({
  1063. source: source,
  1064. style: new Style({
  1065. stroke: new Stroke({
  1066. color: '#AF5',
  1067. width: 5
  1068. })
  1069. })
  1070. });
  1071. this.carLayer.getSource().addFeatures([this.carFeature, this.traceFeature]);
  1072. const move = (e) => {
  1073. const time = e.frameState.time;
  1074. // 时间戳差(毫秒)
  1075. const elapsedTime = time - lastTime;
  1076. // 距离(其实是比例的概念)
  1077. distance = distance + (speed * elapsedTime) / 1e6;
  1078. if (distance >= 1) {
  1079. distance = 0;
  1080. animationFlag = false;
  1081. stopAnimation();
  1082. return;
  1083. }
  1084. // 保存当前时间
  1085. lastTime = time;
  1086. // 上次坐标
  1087. const lastCoord = this.carFeature.getGeometry().getCoordinates();
  1088. // 获取新位置的坐标点
  1089. const curCoord = route.getCoordinateAt(distance);
  1090. // 设置新坐标
  1091. this.carFeature.getGeometry().setCoordinates(curCoord);
  1092. this.map.getView().setCenter(curCoord);
  1093. // 设置角度
  1094. this.carFeature.getStyle().getImage().setRotation(getAngle(lastCoord, curCoord));
  1095. // 调用地图渲染
  1096. this.map.render();
  1097. };
  1098. const stopAnimation = () => {
  1099. this.carLayer.un('postrender', move);
  1100. };
  1101. this.map.addLayer(this.carLayer);
  1102. this.carLayer.on('postrender', move);
  1103. // 触发地图渲染
  1104. const geo = this.carFeature.getGeometry().clone();
  1105. this.carFeature.setGeometry(geo);
  1106. return [this.carLayer];
  1107. }
  1108. drawData(data) {
  1109. const res = [];
  1110. data.forEach((item) => {
  1111. let graphic;
  1112. if (['rectangle', 'polygon', 'anyLine'].includes(item.type)) {
  1113. graphic = this.createPolygon(item);
  1114. graphic.set('id', item.id);
  1115. res.push(graphic);
  1116. } else if (item.type === 'circle') {
  1117. graphic = this.createCircle(item);
  1118. graphic.set('id', item.id);
  1119. res.push(graphic);
  1120. } else if (item.type === 'straightLine') {
  1121. graphic = this.createLineString(item);
  1122. graphic.set('id', item.id);
  1123. res.push(graphic);
  1124. } else if (item.type === 'text') {
  1125. const { text } = this.addText(item);
  1126. res.push(text);
  1127. } else if (item.type === 'measureArea') {
  1128. graphic = this.createPolygon(item);
  1129. graphic.set('id', item.id);
  1130. const style = new Style({
  1131. stroke: new Stroke({
  1132. color: rgbToRgba(item.strokeColor, item.strokeOpacity),
  1133. width: item.strokeWeight
  1134. }),
  1135. fill: new Fill({
  1136. color: rgbToRgba(item.fillColor, item.fillOpacity)
  1137. }),
  1138. text: new Text({
  1139. text: '区域面积' + item.area.toFixed(2) + '平方米',
  1140. font: '14px Calibri,sans-serif',
  1141. fill: new Fill({ color: '#000' }),
  1142. stroke: new Stroke({
  1143. color: '#fff',
  1144. width: 3
  1145. }),
  1146. overflow: true
  1147. })
  1148. });
  1149. graphic.setStyle(style);
  1150. res.push(graphic);
  1151. } else if (item.type === 'marker') {
  1152. // 创建标注点
  1153. const marker = new Feature({
  1154. // 必须是数字类型,字符串不识别
  1155. geometry: new Point([item.longitude, item.latitude]),
  1156. // name: item.name,
  1157. // icon: item.icon,
  1158. // imageHover: item.imageHover,
  1159. // size: item.size,
  1160. pointer: true
  1161. });
  1162. marker.set('id', item.id);
  1163. const img = new Image();
  1164. img.onload = () => {
  1165. // 图片加载完成后,可以访问其 width 和 height 属性
  1166. const width = img.width;
  1167. const height = img.height;
  1168. const style = new Style({
  1169. image: new Icon({
  1170. anchor: [0.5, 1],
  1171. src: item.icon,
  1172. size: [width, height],
  1173. scale: !!item.size[0] ? item.size[0] / width : 1
  1174. }),
  1175. text: new Text({
  1176. text: item.name,
  1177. fill: new Fill({
  1178. color: '#000'
  1179. }),
  1180. stroke: new Stroke({
  1181. color: '#fff',
  1182. width: 3
  1183. })
  1184. })
  1185. });
  1186. marker.setStyle(style);
  1187. };
  1188. img.src = item.icon; // 设置图片的 URL,触发加载
  1189. this.drawVector.getSource().addFeature(marker);
  1190. res.push(marker);
  1191. }
  1192. });
  1193. return res;
  1194. }
  1195. getVectorLayer() {
  1196. return this.vectorLayer;
  1197. }
  1198. getDrawVector() {
  1199. return this.drawVector;
  1200. }
  1201. getMap() {
  1202. return this.map;
  1203. }
  1204. getMouseTool() {
  1205. return this.drawTool;
  1206. }
  1207. setDrawEndMethod(newMethod) {
  1208. this.drawEndMethod = newMethod;
  1209. }
  1210. }