olMap.ts 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152
  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 { 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. constructor(options) {
  93. this.options = options;
  94. this.map = new Map({
  95. controls: defaults({
  96. zoom: false,
  97. rotate: false
  98. }),
  99. layers: [],
  100. target: options.dom,
  101. view: new View({
  102. center: options.center && options.center.length === 2 ? [options.center[0], options.center[1]] : [110.90153121597234, 21.98323671981171],
  103. zoom: options.zoom ? options.zoom : 9.6,
  104. projection: projection,
  105. maxZoom: options.maxZoom ? options.maxZoom : 18,
  106. minZoom: options.minZoom ? options.minZoom : 1
  107. })
  108. });
  109. // 初始化比例尺
  110. if (options.showScale) {
  111. // this.map.addControl(new AMap.Scale());
  112. }
  113. if (options.drawTool?.use) {
  114. this.initMouseTool(options.drawTool);
  115. }
  116. this.initLayer(options);
  117. // 给标注点添加手势
  118. this.map.on('pointermove', (e) => {
  119. this.map.getTargetElement().style.cursor = 'auto';
  120. const features = this.map.getFeaturesAtPixel(e.pixel);
  121. features.forEach((feature) => {
  122. const originalFeature = feature.get('features') ? feature.get('features')[0] : '';
  123. if (!!originalFeature && originalFeature.get('pointer')) {
  124. this.map.getTargetElement().style.cursor = 'pointer'; //设置鼠标样式
  125. } else {
  126. this.map.getTargetElement().style.cursor = 'auto';
  127. }
  128. });
  129. });
  130. // 创建选择交互
  131. this.select = new Select({
  132. condition: click,
  133. filter: (feature, layer) => {
  134. // 只有vectorLayer图层可选择
  135. return layer === this.vectorLayer;
  136. }
  137. });
  138. this.map.addInteraction(this.select);
  139. // 监听Select交互的select事件
  140. this.select.on('select', (event) => {
  141. const selectedFeatures = event.selected[0]; // 获取被选中的要素集合
  142. const features = selectedFeatures.get('features');
  143. if (selectedFeatures && !!features) {
  144. const originalFeature = features[0];
  145. const size = features.length;
  146. if (size === 1) {
  147. // 设置新的样式(更换图标)
  148. selectedFeatures.setStyle(
  149. new Style({
  150. image: new Icon({
  151. anchor: [0.5, 0.5],
  152. scale: 0.146,
  153. anchorXUnits: 'fraction',
  154. anchorYUnits: 'pixels',
  155. src: originalFeature.get('imageHover')
  156. })
  157. })
  158. );
  159. const extData = originalFeature.get('extData');
  160. options.onMarkerClick(extData);
  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. };
  314. const data = getRgba(newOptions.color);
  315. let geometryFunction = null;
  316. if (newOptions.graphicsType === 'rectangle') {
  317. // 绘制矩形的方法
  318. geometryFunction = createBox();
  319. }
  320. if (!!typeList[newOptions.graphicsType]) {
  321. if (newOptions.graphicsType === 'text') {
  322. this.drawOptions = {
  323. type: newOptions.graphicsType,
  324. title: newOptions.title,
  325. text: newOptions.text,
  326. fontSize: newOptions.fontSize,
  327. fontColor: newOptions.fontColor,
  328. lnglat: newOptions.lnglat
  329. };
  330. // 绘制文字
  331. return this.addText(this.drawOptions);
  332. } else {
  333. this.drawOptions = {
  334. type: newOptions.graphicsType,
  335. title: newOptions.title,
  336. strokeColor: !!data.color ? data.color : newOptions.color,
  337. strokeOpacity: 1,
  338. strokeWeight: 1,
  339. fillColor: data.color,
  340. fillOpacity: data.opacity,
  341. strokeStyle: 'solid'
  342. };
  343. if (newOptions.graphicsType === 'marker') {
  344. this.drawOptions.icon = newOptions.icon;
  345. this.drawOptions.iconName = newOptions.iconName;
  346. this.drawOptions.size = newOptions.size;
  347. }
  348. this.closeDraw();
  349. // 创建绘制交互
  350. let style = new Style({
  351. stroke: new Stroke({
  352. color: rgbToRgba(this.drawOptions.strokeColor, this.drawOptions.strokeOpacity),
  353. width: this.drawOptions.strokeWeight
  354. }),
  355. fill: new Fill({
  356. color: rgbToRgba(this.drawOptions.fillColor, this.drawOptions.fillOpacity)
  357. })
  358. });
  359. this.drawTool = new Draw({
  360. source: this.drawVector.getSource(),
  361. type: typeList[newOptions.graphicsType],
  362. geometryFunction: geometryFunction,
  363. style: style
  364. });
  365. // 添加绘制交互到地图
  366. this.map.addInteraction(this.drawTool);
  367. // 监听绘制结束事件
  368. this.drawTool.on('drawend', (event) => {
  369. const feature = event.feature;
  370. // 获取几何对象
  371. if (newOptions.graphicsType !== 'marker') {
  372. if (newOptions.graphicsType === 'measureArea') {
  373. const geometry = feature.getGeometry();
  374. const coordinates = geometry.getCoordinates();
  375. const pathArr = coordinates[0];
  376. const area = turf.area(turf.polygon([pathArr]));
  377. style = new Style({
  378. stroke: style.getStroke(),
  379. fill: style.getFill(),
  380. text: new Text({
  381. text: '区域面积' + area.toFixed(2) + '平方米',
  382. font: '14px Calibri,sans-serif',
  383. fill: new Fill({ color: '#000' }),
  384. stroke: new Stroke({
  385. color: '#fff',
  386. width: 3
  387. }),
  388. overflow: true
  389. })
  390. });
  391. }
  392. feature.setStyle(style);
  393. }
  394. });
  395. }
  396. }
  397. return this.drawOptions;
  398. }
  399. // 空间分析绘制图形
  400. drawGraphics2(newOptions: MouseTool) {
  401. const typeList = {
  402. circle: 'Circle',
  403. rectangle: 'Circle',
  404. polygon: 'Polygon'
  405. };
  406. let geometryFunction = null;
  407. if (newOptions.graphicsType === 'rectangle') {
  408. // 绘制矩形的方法
  409. geometryFunction = createBox();
  410. }
  411. if (!!typeList[newOptions.graphicsType]) {
  412. this.drawOptions = {
  413. type: newOptions.graphicsType,
  414. strokeColor: newOptions.color,
  415. strokeOpacity: 1,
  416. strokeWeight: 1,
  417. fillColor: newOptions.color,
  418. fillOpacity: newOptions.drawType === '1' ? '0' : '0.5',
  419. strokeStyle: 'solid'
  420. };
  421. this.closeDraw();
  422. // 创建绘制交互
  423. const style = new Style({
  424. stroke: new Stroke({
  425. color: hexToRgba(this.drawOptions.strokeColor, this.drawOptions.strokeOpacity),
  426. width: this.drawOptions.strokeWeight
  427. }),
  428. fill: new Fill({
  429. color: hexToRgba(this.drawOptions.fillColor, this.drawOptions.fillOpacity)
  430. })
  431. });
  432. this.drawTool = new Draw({
  433. source: this.drawVector.getSource(),
  434. type: typeList[newOptions.graphicsType],
  435. geometryFunction: geometryFunction,
  436. style: style
  437. });
  438. // 添加绘制交互到地图
  439. this.map.addInteraction(this.drawTool);
  440. // 监听绘制结束事件
  441. this.drawTool.on('drawend', (event) => {
  442. const feature = event.feature;
  443. // 获取几何对象
  444. feature.setStyle(style);
  445. });
  446. }
  447. }
  448. addText(options) {
  449. // 创建文本覆盖物
  450. const style = new Style({
  451. text: new Text({
  452. text: options.text,
  453. font: options.fontSize + ' Calibri,sans-serif',
  454. fill: new Fill({ color: options.fontColor }),
  455. // stroke: new Stroke({
  456. // color: '#fff',
  457. // width: 3
  458. // }),
  459. overflow: true
  460. })
  461. });
  462. const text = new Feature({
  463. geometry: new Point(options.lnglat)
  464. });
  465. text.setStyle(style);
  466. // 将文本覆盖物添加到地图
  467. this.drawVector.getSource().addFeature(text);
  468. const id = nanoid();
  469. text.set('id', id);
  470. const data: any = deepClone(options);
  471. data.id = id;
  472. return { text, data };
  473. }
  474. // 关闭绘制
  475. closeDraw() {
  476. if (!this.drawTool) return;
  477. this.map.removeInteraction(this.drawTool);
  478. // this.drawTool.abortDrawing();
  479. }
  480. // 切换图层
  481. async replaceLayers(newLayers, loadendFunc) {
  482. // 遍历当前的所有图层并移除它们
  483. this.map.getLayers().forEach((layer) => {
  484. this.map.removeLayer(layer);
  485. });
  486. if (Array.isArray(newLayers)) {
  487. for (const layer of newLayers) {
  488. if (typeof layer === 'string') {
  489. await this.formatXml(layer); // 等待当前 layer 处理完成
  490. } else {
  491. await this.formatXml(layer.id, layer.minZoom, layer.maxZoom, layer.zIndex, layer.visible); // 等待当前 layer 处理完成
  492. }
  493. }
  494. } else {
  495. // 如果 options.id 不是数组,但确实是一个图层,则直接处理
  496. await this.formatXml(newLayers.id, newLayers.minZoom, newLayers.maxZoom, newLayers.zIndex, newLayers.visible);
  497. }
  498. // 创建Vector层并添加到地图上
  499. this.vectorLayer = new VectorLayer({
  500. source: new VectorSource({
  501. features: []
  502. })
  503. });
  504. this.map.addLayer(this.vectorLayer);
  505. const point = JSON.parse(JSON.stringify(this.markers));
  506. this.markers = [];
  507. this.addMarker(point);
  508. if (loadendFunc) {
  509. loadendFunc();
  510. }
  511. }
  512. // 集群点样式
  513. clusterStyle(feature) {
  514. const originalFeature = feature.get('features')[0];
  515. const size = feature.get('features').length;
  516. if (size > 1) {
  517. const outerCircle = new CircleStyle({
  518. radius: 20,
  519. fill: new Fill({
  520. color: 'rgba(79, 176, 206, 0.5)'
  521. }),
  522. stroke: new Stroke({
  523. color: 'rgba(79, 176, 206, 1)'
  524. })
  525. });
  526. return [
  527. new Style({
  528. image: outerCircle,
  529. text: new Text({
  530. text: size.toString(),
  531. font: '14px sans-serif',
  532. fill: new Fill({
  533. color: '#ffff'
  534. })
  535. })
  536. })
  537. ];
  538. }
  539. return new Style({
  540. geometry: originalFeature.getGeometry(),
  541. image: new Icon({
  542. anchor: [0.5, 0.5],
  543. scale: 0.146,
  544. anchorXUnits: 'fraction',
  545. anchorYUnits: 'pixels',
  546. src: originalFeature.get('icon')
  547. })
  548. });
  549. }
  550. addMarker(points) {
  551. this.clearMarker();
  552. const features = [];
  553. points.forEach((point) => {
  554. // 创建标注点
  555. const feature = new Feature({
  556. // 必须是数字类型,字符串不识别
  557. geometry: new Point([Number(point.longitude), Number(point.latitude)]),
  558. name: point.name,
  559. icon: point.icon,
  560. imageHover: point.imageHover,
  561. size: point.size,
  562. pointer: true,
  563. extData: point
  564. });
  565. // 设置自定义属性
  566. feature.set('pointer', true);
  567. const img = new Image();
  568. img.onload = () => {
  569. // 图片加载完成后,可以访问其 width 和 height 属性
  570. const width = img.width;
  571. const height = img.height;
  572. const style = new Style({
  573. image: new Icon({
  574. anchor: [0.5, 0.5],
  575. anchorXUnits: 'fraction',
  576. anchorYUnits: 'pixels',
  577. src: point.icon,
  578. size: [width, height],
  579. scale: !!point.size[0] ? point.size[0] / width : 1
  580. }),
  581. text: new Text({
  582. text: point.name,
  583. fill: new Fill({
  584. color: '#000'
  585. }),
  586. stroke: new Stroke({
  587. color: '#fff',
  588. width: 3
  589. })
  590. })
  591. });
  592. features.setStyle(style);
  593. };
  594. img.src = point.icon; // 设置图片的 URL,触发加载
  595. features.push(feature);
  596. this.markers.push(point);
  597. });
  598. const vectorSource = new VectorSource({
  599. features: features
  600. });
  601. const clusterSource = new Cluster({
  602. distance: 30,
  603. source: vectorSource
  604. });
  605. this.vectorLayer.setStyle(this.clusterStyle);
  606. this.vectorLayer.setSource(clusterSource);
  607. }
  608. // 清除所有标加
  609. clearMarker() {
  610. if (!this.vectorLayer) return;
  611. this.vectorLayer.getSource().clear();
  612. }
  613. showInfo(content, position, isCustom) {
  614. this.hideInfo();
  615. if (!this.infoWindow) {
  616. this.infoWindow = new Overlay({
  617. element: content,
  618. positioning: 'bottom-center', // 你可以根据需要调整定位方式
  619. offset: [0, -10] // 偏移量,用于调整覆盖层相对于要素的位置
  620. });
  621. }
  622. this.infoWindow.setPosition(position);
  623. initDrag(this.infoWindow.element);
  624. this.map.addOverlay(this.infoWindow);
  625. }
  626. hideInfo(flag) {
  627. this.map.removeOverlay(this.infoWindow);
  628. this.infoWindow = null;
  629. if (!!flag && this.select) {
  630. this.select.getFeatures().clear();
  631. }
  632. }
  633. /**
  634. *
  635. * @param {Geojon} chaozhou 根据geojson对象创建Featrue对象
  636. * @returns VectorLayer
  637. */
  638. createVecByJson(json, options) {
  639. const format = new GeoJSON();
  640. const fs = format.readFeatures(json);
  641. this.maskLayer = new VectorLayer({
  642. source: new VectorSource(),
  643. style: new Style({
  644. fill: new Fill({
  645. color: options.fillColor ? options.fillColor : 'rgba(16, 36, 59, 0.65)'
  646. }),
  647. stroke: new Stroke({
  648. color: options.strokeColor ? options.strokeColor : 'rgba(38, 138, 185, 1)',
  649. width: 2
  650. })
  651. }),
  652. zIndex: options.zIndex ? options.zIndex : 99
  653. });
  654. this.map.addLayer(this.maskLayer);
  655. const extent = [-180, -90, 180, 90];
  656. const polygonRing = fromExtent(extent);
  657. fs.forEach((x) => {
  658. const ft = x.values_.geometry;
  659. const coords = ft.getCoordinates();
  660. coords.forEach((coord) => {
  661. const linearRing = new LinearRing(coord[0]);
  662. polygonRing.appendLinearRing(linearRing);
  663. });
  664. });
  665. const convertFt = new Feature({
  666. geometry: polygonRing
  667. });
  668. this.maskLayer.getSource().addFeature(convertFt);
  669. }
  670. createVecByJson2(json, options) {
  671. if (!!this.maskLayer2) {
  672. // this.map.addLayer(this.maskLayer);
  673. this.map.addLayer(this.maskLayer2);
  674. } else {
  675. this.maskLayer2 = new VectorLayer({
  676. source: new VectorSource(),
  677. style: new Style({
  678. fill: new Fill({
  679. color: 'rgba(0, 0, 0, 0)'
  680. }),
  681. stroke: new Stroke({
  682. color: options.strokeColor ? options.strokeColor : '#268ab9',
  683. width: options.strokeWeight ? options.strokeWeight : 1
  684. })
  685. })
  686. });
  687. this.map.addLayer(this.maskLayer2);
  688. // this.maskLayer = new VectorLayer({
  689. // source: new VectorSource(),
  690. // style: new Style({
  691. // fill: new Fill({
  692. // color: options.fillColor ? options.fillColor : 'rgba(16, 36, 59, 0.65)'
  693. // }),
  694. // stroke: new Stroke({
  695. // color: options.strokeColor ? options.strokeColor : 'rgba(38, 138, 185, 1)',
  696. // width: 2
  697. // })
  698. // }),
  699. // zIndex: options.zIndex ? options.zIndex : 99
  700. // });
  701. // // 合并区边界
  702. // const format = new GeoJSON();
  703. // const data2 = mergeGeoJsonPolygons(json);
  704. // const fs = format.readFeatures(data2);
  705. // const extent = [-180, -90, 180, 90];
  706. // const polygonRing = fromExtent(extent);
  707. // fs.forEach((x) => {
  708. // const ft = x.values_.geometry;
  709. // const coords = ft.getCoordinates();
  710. // coords.forEach((coord) => {
  711. // const linearRing = new LinearRing(coord[0]);
  712. // polygonRing.appendLinearRing(linearRing);
  713. // });
  714. // });
  715. // const convertFt = new Feature({
  716. // geometry: polygonRing
  717. // });
  718. // this.maskLayer.getSource().addFeature(convertFt);
  719. // this.map.addLayer(this.maskLayer);
  720. // 边界部分
  721. json.features.forEach((feature) => {
  722. if (feature.geometry.type === 'Polygon') {
  723. const feature2 = new Feature({
  724. geometry: new LineString(feature.geometry.coordinates[0])
  725. });
  726. this.maskLayer2.getSource().addFeature(feature2);
  727. } else if (feature.geometry.type === 'MultiPolygon') {
  728. feature.geometry.coordinates.forEach((polygonCoords) => {
  729. const feature2 = new Feature({
  730. geometry: new LineString(polygonCoords[0])
  731. });
  732. this.maskLayer2.getSource().addFeature(feature2);
  733. });
  734. }
  735. });
  736. }
  737. }
  738. /**
  739. * @description 创建矢量图层
  740. * @param {String} layerName 图层名称
  741. * @param {Number} zIndex 地图层级默认是0
  742. * @returns
  743. */
  744. createVecLayer(layerName = '', zIndex = 0) {
  745. const source = new SourceVector({
  746. crossOrigin: 'anonymous'
  747. });
  748. const layer = new Vector({
  749. source,
  750. zIndex
  751. });
  752. layer.set('layerName', layerName);
  753. return layer;
  754. }
  755. // 分布图遮罩层
  756. createMask(data) {
  757. this.removeMask();
  758. if (!data || data.length === 0) return;
  759. data.forEach((item) => {
  760. if (!item.points || item.points.length === 0) return;
  761. // 遮罩图层的样式
  762. const maskStyle = new Style({
  763. fill: new Fill({
  764. color: item.color // 红色遮罩,50%透明度
  765. }),
  766. stroke: new Stroke({
  767. color: 'rgba(159,159,159,0.7)',
  768. width: 1
  769. })
  770. });
  771. // 遮罩图层的矢量数据源(初始为空)
  772. const maskSource = new VectorSource();
  773. // 创建一个多边形特征
  774. const polygonFeature = new Feature({
  775. geometry: new Polygon(item.points)
  776. });
  777. const maskLayer = new VectorLayer({
  778. source: maskSource,
  779. style: maskStyle,
  780. properties: {
  781. name: 'mask'
  782. }
  783. });
  784. this.map.addLayer(maskLayer);
  785. // 将多边形特征添加到遮罩数据源中
  786. maskSource.addFeature(polygonFeature);
  787. });
  788. }
  789. removeMask() {
  790. //移除图层
  791. const layersArray = this.map.getLayers().getArray();
  792. layersArray.forEach((layer) => {
  793. // 检查图层是否有自定义属性,并且该属性是否匹配你要移除的图层的标识符
  794. if (layer.get('name') === 'mask') {
  795. this.map.removeLayer(layer);
  796. }
  797. });
  798. }
  799. removeMask2() {
  800. if (this.maskLayer) {
  801. this.map.removeLayer(this.maskLayer);
  802. this.maskLayer = null;
  803. }
  804. }
  805. removeMask3(isHide) {
  806. if (this.maskLayer) {
  807. this.map.removeLayer(this.maskLayer);
  808. if (!isHide) {
  809. this.maskLayer = [];
  810. }
  811. }
  812. if (this.maskLayer2) {
  813. this.map.removeLayer(this.maskLayer2);
  814. if (!isHide) {
  815. this.maskLayer2 = [];
  816. }
  817. }
  818. }
  819. /**
  820. * 绘制经纬线
  821. * visible: boolean 是否可见
  822. */
  823. handleLngLatLine(visible: boolean) {
  824. // 创建经纬网图层
  825. const graticule = new Graticule({
  826. name: 'Graticule',
  827. showLabels: true, // 为每条刻度线绘制一个带有各自纬度/经度的标签
  828. wrapX: false, // 是否水平重复经纬网
  829. targetSize: 230,
  830. zIndex: 99999,
  831. strokeStyle: new Stroke({
  832. // 用于绘制刻度线的样式
  833. color: '#dcdcdc', // 线条颜色
  834. width: 1 // 线条宽度
  835. }),
  836. lonLabelStyle: new Text({
  837. font: '12px Calibri,sans-serif',
  838. textBaseline: 'bottom',
  839. fill: new Fill({
  840. color: '#9d9d9d'
  841. })
  842. }),
  843. latLabelPosition: 0,
  844. latLabelStyle: new Text({
  845. font: '12px Calibri,sans-serif',
  846. textAlign: 'left',
  847. textBaseline: 'end',
  848. fill: new Fill({
  849. color: '#9d9d9d'
  850. })
  851. })
  852. });
  853. if (visible) {
  854. this.map.addLayer(graticule);
  855. } else {
  856. this.removeLayer('Graticule');
  857. }
  858. }
  859. /**
  860. * 移除指定name的layer
  861. * layerName: string
  862. * */
  863. removeLayer(layerName: string) {
  864. const layers = this.map.getLayers();
  865. layers.forEach((element) => {
  866. if (!!element && element.get('name') === layerName) {
  867. //移除
  868. this.map.removeLayer(element);
  869. }
  870. });
  871. }
  872. // 创建图形
  873. createGraphics(data: any) {
  874. if (data.type === 'circle') {
  875. // 绘制圆形
  876. return this.createCircle(data);
  877. } else if (['polygon', 'rectangle'].includes(data.type)) {
  878. // 绘制矩形、多边形
  879. return this.createPolygon(data);
  880. }
  881. // else if (data.type === 'marker') {
  882. // // 绘制图标
  883. // return createMarker(data);
  884. // } else if (data.type === 'measureArea') {
  885. // // 绘制面积
  886. // return createMeasureArea(data);
  887. // } else if (data.type === 'text') {
  888. // // 文字
  889. // return addText(data);
  890. // } else if (data.type === 'straightLine') {
  891. // // 直线
  892. // return createStraightLine(data);
  893. // }
  894. }
  895. createCircle(data) {
  896. const circle = new Circle(data.center, data.radius);
  897. const feature = new Feature(circle);
  898. feature.setStyle(
  899. new Style({
  900. stroke: new Stroke({
  901. color: rgbToRgba(data.strokeColor, data.strokeOpacity),
  902. width: data.strokeWeight
  903. }),
  904. fill: new Fill({
  905. color: rgbToRgba(data.fillColor, data.fillOpacity)
  906. })
  907. })
  908. );
  909. this.drawVector.getSource().addFeature(feature);
  910. return feature;
  911. }
  912. createPolygon(data) {
  913. const polygon = new Polygon([data.path]);
  914. const feature = new Feature(polygon);
  915. feature.setStyle(
  916. new Style({
  917. stroke: new Stroke({
  918. color: rgbToRgba(data.strokeColor, data.strokeOpacity),
  919. width: data.strokeWeight
  920. }),
  921. fill: new Fill({
  922. color: rgbToRgba(data.fillColor, data.fillOpacity)
  923. })
  924. })
  925. );
  926. this.drawVector.getSource().addFeature(feature);
  927. return feature;
  928. }
  929. createLineString(data) {
  930. const lineString = new LineString(data.path);
  931. const feature = new Feature(lineString);
  932. feature.setStyle(
  933. new Style({
  934. stroke: new Stroke({
  935. color: rgbToRgba(data.strokeColor, data.strokeOpacity),
  936. width: data.strokeWeight
  937. }),
  938. fill: new Fill({
  939. color: rgbToRgba(data.fillColor, data.fillOpacity)
  940. })
  941. })
  942. );
  943. this.drawVector.getSource().addFeature(feature);
  944. return feature;
  945. }
  946. trackPlayback(lineArr) {
  947. if (!!this.carFeature) {
  948. this.carLayer.getSource().removeFeature(this.carFeature);
  949. }
  950. if (!!this.traceFeature) {
  951. this.carLayer.getSource().removeFeature(this.traceFeature);
  952. }
  953. const getAngle = (point1, point2) => {
  954. let arc = 0;
  955. if (point2 && point2.length && point1 && point1.length) {
  956. if ((point2[0] - point1[0] >= 0 && point2[1] - point1[1] >= 0) || (point2[0] - point1[0] < 0 && point2[1] - point1[1] > 0)) {
  957. arc = Math.atan((point2[0] - point1[0]) / (point2[1] - point1[1]));
  958. } else if ((point2[0] - point1[0] > 0 && point2[1] - point1[1] < 0) || (point2[0] - point1[0] < 0 && point2[1] - point1[1] < 0)) {
  959. arc = Math.PI + Math.atan((point2[0] - point1[0]) / (point2[1] - point1[1]));
  960. }
  961. }
  962. return arc;
  963. };
  964. let lastTime = Date.now();
  965. const source = new VectorSource();
  966. let distance = 0;
  967. const angle = getAngle(lineArr[0], lineArr[1]);
  968. const speed = 500;
  969. let animationFlag = false;
  970. this.carFeature = new Feature({
  971. geometry: new Point(lineArr[0])
  972. });
  973. const icon = new Icon({
  974. crossOrigin: 'anonymous',
  975. src: carImg,
  976. width: 26,
  977. height: 52,
  978. rotation: angle
  979. });
  980. this.carFeature.setStyle(
  981. new Style({
  982. image: icon
  983. })
  984. );
  985. this.traceFeature = new Feature({
  986. geometry: new LineString(lineArr)
  987. });
  988. let route = new LineString(lineArr);
  989. this.carLayer = new VectorLayer({
  990. source: source,
  991. style: new Style({
  992. stroke: new Stroke({
  993. color: 'rgb(37,232,142)',
  994. width: 5
  995. })
  996. })
  997. });
  998. this.carLayer.getSource().addFeatures([this.carFeature, this.traceFeature]);
  999. const move = (e) => {
  1000. const time = e.frameState.time;
  1001. // 时间戳差(毫秒)
  1002. const elapsedTime = time - lastTime;
  1003. // 距离(其实是比例的概念)
  1004. distance = distance + (speed * elapsedTime) / 1e6;
  1005. if (distance >= 1) {
  1006. distance = 0;
  1007. animationFlag = false;
  1008. stopAnimation();
  1009. return;
  1010. }
  1011. // 保存当前时间
  1012. lastTime = time;
  1013. // 上次坐标
  1014. const lastCoord = this.carFeature.getGeometry().getCoordinates();
  1015. // 获取新位置的坐标点
  1016. const curCoord = route.getCoordinateAt(distance);
  1017. // 设置新坐标
  1018. this.carFeature.getGeometry().setCoordinates(curCoord);
  1019. this.map.getView().setCenter(curCoord);
  1020. // 设置角度
  1021. this.carFeature.getStyle().getImage().setRotation(getAngle(lastCoord, curCoord));
  1022. // 调用地图渲染
  1023. this.map.render();
  1024. };
  1025. const stopAnimation = () => {
  1026. this.carLayer.un('postrender', move);
  1027. };
  1028. this.map.addLayer(this.carLayer);
  1029. this.carLayer.on('postrender', move);
  1030. // 触发地图渲染
  1031. const geo = this.carFeature.getGeometry().clone();
  1032. this.carFeature.setGeometry(geo);
  1033. }
  1034. drawData(data) {
  1035. const res = [];
  1036. data.forEach((item) => {
  1037. let graphic;
  1038. if (['rectangle', 'polygon', 'anyLine'].includes(item.type)) {
  1039. graphic = this.createPolygon(item);
  1040. graphic.set('id', item.id);
  1041. res.push(graphic);
  1042. } else if (item.type === 'circle') {
  1043. graphic = this.createCircle(item);
  1044. graphic.set('id', item.id);
  1045. res.push(graphic);
  1046. } else if (item.type === 'straightLine') {
  1047. graphic = this.createLineString(item);
  1048. graphic.set('id', item.id);
  1049. res.push(graphic);
  1050. } else if (item.type === 'text') {
  1051. const { text } = this.addText(item);
  1052. res.push(text);
  1053. } else if (item.type === 'measureArea') {
  1054. graphic = this.createPolygon(item);
  1055. graphic.set('id', item.id);
  1056. const style = new Style({
  1057. stroke: new Stroke({
  1058. color: rgbToRgba(item.strokeColor, item.strokeOpacity),
  1059. width: item.strokeWeight
  1060. }),
  1061. fill: new Fill({
  1062. color: rgbToRgba(item.fillColor, item.fillOpacity)
  1063. }),
  1064. text: new Text({
  1065. text: '区域面积' + item.area.toFixed(2) + '平方米',
  1066. font: '14px Calibri,sans-serif',
  1067. fill: new Fill({ color: '#000' }),
  1068. stroke: new Stroke({
  1069. color: '#fff',
  1070. width: 3
  1071. }),
  1072. overflow: true
  1073. })
  1074. });
  1075. graphic.setStyle(style);
  1076. res.push(graphic);
  1077. } else if (item.type === 'marker') {
  1078. // 创建标注点
  1079. const marker = new Feature({
  1080. // 必须是数字类型,字符串不识别
  1081. geometry: new Point([item.longitude, item.latitude]),
  1082. // name: item.name,
  1083. // icon: item.icon,
  1084. // imageHover: item.imageHover,
  1085. // size: item.size,
  1086. pointer: true
  1087. });
  1088. marker.set('id', item.id);
  1089. const img = new Image();
  1090. img.onload = () => {
  1091. // 图片加载完成后,可以访问其 width 和 height 属性
  1092. const width = img.width;
  1093. const height = img.height;
  1094. const style = new Style({
  1095. image: new Icon({
  1096. anchor: [0.5, 0.5],
  1097. anchorXUnits: 'fraction',
  1098. anchorYUnits: 'pixels',
  1099. src: item.icon,
  1100. size: [width, height],
  1101. scale: !!item.size[0] ? item.size[0] / width : 1
  1102. }),
  1103. text: new Text({
  1104. text: item.name,
  1105. fill: new Fill({
  1106. color: '#000'
  1107. }),
  1108. stroke: new Stroke({
  1109. color: '#fff',
  1110. width: 3
  1111. })
  1112. })
  1113. });
  1114. marker.setStyle(style);
  1115. };
  1116. img.src = item.icon; // 设置图片的 URL,触发加载
  1117. this.drawVector.getSource().addFeature(marker);
  1118. res.push(marker);
  1119. }
  1120. });
  1121. return res;
  1122. }
  1123. getVectorLayer() {
  1124. return this.vectorLayer;
  1125. }
  1126. getDrawVector() {
  1127. return this.drawVector;
  1128. }
  1129. getMap() {
  1130. return this.map;
  1131. }
  1132. getMouseTool() {
  1133. return this.drawTool;
  1134. }
  1135. }