import * as KMap from "@/utils/ol-map/KMap"; import * as util from "@/common/ol_common.js"; import config from "@/api/config.js"; import StaticImgLayer from "@/utils/ol-map/StaticImgLayer"; import Style from "ol/style/Style"; import Icon from "ol/style/Icon"; import Fill from "ol/style/Fill"; import Stroke from "ol/style/Stroke"; import { Point, Polygon } from "ol/geom"; import Feature from "ol/Feature"; import Overlay from "ol/Overlay"; import WKT from "ol/format/WKT.js"; import { reactive } from "vue"; export let mapLocation = reactive({ data: null, }); /** 无范围时中心示意方框边长(米) */ const NO_RANGE_SIDE_METERS = 280; /** * @description 地图层对象 */ class IndexMap { constructor() { let that = this; let vectorStyle = new KMap.VectorStyle(); this.vectorStyle = vectorStyle; this.noRangeTipOverlay = null; this.remoteImageLayer = null; // 位置图标 this.clickPointLayer = new KMap.VectorLayer("clickPointLayer", 9999, { style: (f) => { return new Style({ image: new Icon({ src: require("@/assets/img/home/garden-point.png"), scale: 0.5, }), }); }, }); // 无勾画范围:中心点示意正方形 this.noRangeGuideLayer = new KMap.VectorLayer("noRangeGuideLayer", 9998, { style: () => new Style({ fill: new Fill({ color: "rgba(0, 0, 0, 0.28)" }), stroke: new Stroke({ color: "rgba(255, 255, 255, 0.95)", width: 1.5, lineDash: [6, 4], }), }), }); } /** 叠加静态风险底图(与 demo growthReportMap.showRemoteImage 一致) */ showRemoteImage(url, extent, options = {}) { this.hideRemoteImage(); if (!url || !extent?.length || !this.kmap) return; this.remoteImageLayer = new StaticImgLayer( url, { extent, minZoom: 6, maxZoom: 22, opacity: options.opacity ?? 0.5, }, 50, this.kmap, ); } hideRemoteImage() { if (this.remoteImageLayer?.layer && this.kmap?.map) { this.kmap.map.removeLayer(this.remoteImageLayer.layer); } this.remoteImageLayer = null; } initMap(location, target) { let level = 16; let coordinate = util.wktCastGeom(location).getFirstCoordinate(); this.kmap = new KMap.Map(target, level, coordinate[0], coordinate[1], null, 8, 22); let xyz2 = config.base_img_url3 + "map/lby/{z}/{x}/{y}.png"; this.kmap.addXYZLayer(xyz2, { minZoom: 8, maxZoom: 22 }, 2); this.kmap.addLayer(this.noRangeGuideLayer.layer); this.kmap.addLayer(this.clickPointLayer.layer); this.setMapPoint(coordinate) // this.addMapSingerClick() } setMapPosition(center) { this.kmap.getView().animate({ center, zoom: 16, duration: 0, }); this.setMapPoint(center) } setMapPoint(coordinate) { this.clickPointLayer.source.clear() let point = new Feature(new Point(coordinate)) this.clickPointLayer.addFeature(point) mapLocation.data = coordinate // 示意框跟着中心点走 if (this.noRangeGuideLayer?.source?.getFeatures?.()?.length) { this.drawNoRangeSquare(coordinate); this.updateNoRangeTipPosition(coordinate); } } /** 以中心点生成正方形环坐标(EPSG:4326) */ buildSquareRing(center, sideLengthMeters = NO_RANGE_SIDE_METERS) { const lng = Number(center[0]); const lat = Number(center[1]); const half = sideLengthMeters / 2; const latDelta = half / 111000; const lngDelta = half / (111000 * Math.cos((lat * Math.PI) / 180)); return [ [lng - lngDelta, lat + latDelta], [lng + lngDelta, lat + latDelta], [lng + lngDelta, lat - latDelta], [lng - lngDelta, lat - latDelta], [lng - lngDelta, lat + latDelta], ]; } drawNoRangeSquare(coordinate) { if (!this.noRangeGuideLayer?.source || !coordinate?.length) return; this.noRangeGuideLayer.source.clear(); const ring = this.buildSquareRing(coordinate); const feature = new Feature(new Polygon([ring])); this.noRangeGuideLayer.addFeature(feature); } updateNoRangeTipPosition(coordinate) { if (!this.noRangeTipOverlay || !coordinate?.length) return; const ring = this.buildSquareRing(coordinate); // 方框底边中点略偏下,提示贴在方框下方 const tipCoord = [(ring[2][0] + ring[3][0]) / 2, ring[2][1] - (ring[0][1] - ring[2][1]) * 0.08]; this.noRangeTipOverlay.setPosition(tipCoord); } /** 无范围引导:中心示意方框 + 下方提示 Overlay */ showNoRangeGuide(coordinate, tipEl) { if (!this.kmap?.map || !coordinate?.length) return; this.drawNoRangeSquare(coordinate); if (tipEl) { if (!this.noRangeTipOverlay) { this.noRangeTipOverlay = new Overlay({ element: tipEl, positioning: "top-center", offset: [0, 10], stopEvent: true, }); this.kmap.map.addOverlay(this.noRangeTipOverlay); } else if (this.noRangeTipOverlay.getElement() !== tipEl) { this.noRangeTipOverlay.setElement(tipEl); } this.updateNoRangeTipPosition(coordinate); } } clearNoRangeGuide() { this.noRangeGuideLayer?.source?.clear?.(); if (this.noRangeTipOverlay) { this.noRangeTipOverlay.setPosition(undefined); } } /** 当前地图点位(优先标记点,其次视图中心) */ getCurrentCoordinate() { const features = this.clickPointLayer?.source?.getFeatures?.() || []; if (features.length) { return features[0].getGeometry()?.getCoordinates?.(); } return this.kmap?.getView?.()?.getCenter?.() || mapLocation.data || null; } toPointWkt(coordinate) { if (!coordinate?.length) return null; return `POINT(${Number(coordinate[0])} ${Number(coordinate[1])})`; } // 地图点击事件 addMapSingerClick() { let that = this; that.kmap.on("singleclick", (evt) => { // that.kmap.map.forEachFeatureAtPixel(evt.pixel, function (feature, layer) { // if ( layer instanceof VectorLayer && layer.get("name") === "reportPolygonLayer" ) { // areaId.data = feature.get("id") // } // }); that.setMapPoint(evt.coordinate) mapLocation.data = evt.coordinate }); } clearLayer() { // this.kmap.removeLayer(this.clickPointLayer.layer) this.kmap.polygonLayer.source.clear(); } createFarmPolygonStyle() { return new Style({ fill: new Fill({ color: "rgba(19, 162, 127, 0.25)", }), stroke: new Stroke({ color: "#13a27f", width: 2, }), }); } setAreaGeometry(geometryArr) { this.clearLayer(); const format = new WKT(); const mapProjection = this.kmap.map.getView().getProjection(); geometryArr.forEach((item) => { const geometry = format.readGeometry(item, { dataProjection: "EPSG:4326", featureProjection: mapProjection, }); const feature = new Feature({ geometry }); feature.setStyle(this.createFarmPolygonStyle()); this.kmap.polygonLayer.source.addFeature(feature); }); this.scheduleFitView(); } fitView(options = {}) { const map = this.kmap?.map; if (!map) return; map.updateSize(); const extent = this.kmap.polygonLayer.source.getExtent(); if (!extent || !isFinite(extent[0])) return; map.getView().fit(extent, { duration: options.duration ?? 0, padding: options.padding ?? [120, 48, 48, 48], maxZoom: options.maxZoom ?? 18, }); } scheduleFitView(options = {}) { const run = () => this.fitView(options); requestAnimationFrame(() => { setTimeout(run, 50); }); } } export default IndexMap;