import * as KMap from '@/utils/ol-map/KMap.js' import * as util from '@/common/ol_common.js' import VectorLayer from 'ol/layer/Vector.js' import { Vector as VectorSource } from 'ol/source' import { newRegionFeature } from '@/utils/map.js' import Style from 'ol/style/Style' import Fill from 'ol/style/Fill' import Stroke from 'ol/style/Stroke' import Overlay from 'ol/Overlay' import { getCenter } from 'ol/extent' import { createRegionInfoCard, updateRegionInfoCard } from './regionInfoCard.js' const FILL_DEFAULT = 'rgba(0, 0, 0, 0.06)' const FILL_SELECTED = 'rgba(255, 166, 23, 0.25)' const STROKE_DEFAULT = '#ffffff' const STROKE_SELECTED = '#FFA617' /** * 地图分区图层 + 分区信息框 */ class RegionLayer { constructor(kmap, options = {}) { const that = this this.kmap = kmap this.enterSelectTree = !!options.enterSelectTree this.onRegionSelect = options.onRegionSelect this.selectedRegionId = null this.regionOverlays = [] this._regionClickHandler = null this._selectedEntry = null this._styleCache = { normal: new Style({ fill: new Fill({ color: FILL_DEFAULT }), stroke: new Stroke({ color: STROKE_DEFAULT, width: 1 }), }), selected: new Style({ fill: new Fill({ color: FILL_SELECTED }), stroke: new Stroke({ color: STROKE_SELECTED, width: 2 }), }), } this.regionLayer = new KMap.VectorLayer('regionLayer', 99, { minZoom: 14.6, renderMode: 'image', updateWhileAnimating: false, updateWhileInteracting: false, style: (f) => that.getRegionStyle(f), }) kmap.addLayer(this.regionLayer.layer) } get map() { return this.kmap.map } getRegionStyle(feature) { return feature.get('selected') ? [this._styleCache.selected] : [this._styleCache.normal] } getFeatureCenter(feature, item) { if (item?.pointWkt) { try { return util.wktCastGeom(item.pointWkt).getFirstCoordinate() } catch { /* ignore */ } } const geom = feature.getGeometry() if (!geom) return null const type = geom.getType() if (type === 'Polygon' && typeof geom.getInteriorPoint === 'function') { return geom.getInteriorPoint().getCoordinates() } if (type === 'MultiPolygon' && typeof geom.getInteriorPoints === 'function') { const points = geom.getInteriorPoints() return points.getPoint(0).getCoordinates() } return getCenter(geom.getExtent()) } clearRegionInfoOverlays() { this.regionOverlays.forEach(({ overlay }) => { this.map.removeOverlay(overlay) }) this.regionOverlays = [] } clearRegionClick() { if (this._regionClickHandler) { this.kmap.off('singleclick', this._regionClickHandler) this._regionClickHandler = null } } bindRegionClick() { this.clearRegionClick() const that = this this._regionClickHandler = (evt) => { if (!that.enterSelectTree) return let hit = null that.map.forEachFeatureAtPixel( evt.pixel, (feature, layer) => { if (layer !== that.regionLayer.layer) return const entry = that.regionOverlays.find((r) => r.feature === feature) if (entry) hit = entry }, { layerFilter: (layer) => layer === that.regionLayer.layer, hitTolerance: 5, }, ) if (hit) that.selectRegion(hit) } this.kmap.on('singleclick', this._regionClickHandler) } selectRegion(entry) { if (!this.enterSelectTree || !entry) return if (this._selectedEntry === entry) return const prev = this._selectedEntry if (prev) { prev.feature.set('selected', false) updateRegionInfoCard(prev.el, { enterSelectTree: true, selected: false, }) } entry.feature.set('selected', true) this._selectedEntry = entry this.selectedRegionId = entry.item.id ?? entry.item.regionId ?? entry.feature.getId() updateRegionInfoCard(entry.el, { enterSelectTree: true, selected: true, }) // 合并为一次图层刷新,减轻 Canvas 矢量层整层重绘闪烁 const source = this.regionLayer.layer.getSource() if (source) { source.changed() } if (typeof this.onRegionSelect === 'function') { this.onRegionSelect(entry.item, entry.feature) } } addRegionInfoOverlay(item, feature) { const coord = this.getFeatureCenter(feature, item) if (!coord) return null const entry = { item, feature, el: null, overlay: null } const el = createRegionInfoCard(item, { enterSelectTree: this.enterSelectTree, selected: false, onClick: () => this.selectRegion(entry), }) entry.el = el const overlay = new Overlay({ element: el, position: coord, positioning: 'bottom-center', stopEvent: this.enterSelectTree, offset: [0, -4], }) entry.overlay = overlay this.map.addOverlay(overlay) if (this.enterSelectTree) this.bindCardClick(entry) return entry } bindCardClick(entry) { if (!entry.el || entry.el._regionSelectBound) return entry.el.addEventListener('click', (e) => { e.stopPropagation() if (this.enterSelectTree) this.selectRegion(entry) }) entry.el._regionSelectBound = true } setEnterSelectTree(val) { this.enterSelectTree = !!val this.regionOverlays.forEach((r) => { updateRegionInfoCard(r.el, { enterSelectTree: this.enterSelectTree, selected: !!r.feature.get('selected'), }) if (r.el) { r.el.style.pointerEvents = this.enterSelectTree ? 'auto' : 'none' if (this.enterSelectTree) this.bindCardClick(r) } if (r.overlay?.setStopEvent) { r.overlay.setStopEvent(this.enterSelectTree) } }) if (this.enterSelectTree) { this.bindRegionClick() } else { this.clearRegionClick() this.regionOverlays.forEach((r) => { r.feature.set('selected', false) updateRegionInfoCard(r.el, { enterSelectTree: false, selected: false }) }) this.selectedRegionId = null this._selectedEntry = null this.regionLayer.layer.getSource()?.changed() } } initData(data) { this.clearLayer() if (!data || !data.length) return const features = [] for (const item of data) { item.regionWkt = item.regionWkt || item.wkt if (!item.regionWkt) { console.warn('[regionLayer] 分区缺少 wkt', item) continue } try { const f = newRegionFeature({ ...item, bgName: 'defalut', bgColor: 'defalut', selected: false, }) features.push(f) const entry = this.addRegionInfoOverlay(item, f) if (entry) { this.regionOverlays.push(entry) } } catch (err) { console.warn('[regionLayer] 解析分区失败', item, err) } } this.area = features const source = new VectorSource({ features }) this.regionLayer.layer.setSource(source) if (features.length) { const extent = source.getExtent() if (extent.every((n) => Number.isFinite(n))) { this.kmap.getView().fit(extent, { padding: [60, 40, 100, 40], duration: 600, maxZoom: 19, }) } } if (this.enterSelectTree) { this.bindRegionClick() } } clearLayer() { this.clearRegionClick() this.clearRegionInfoOverlays() this.selectedRegionId = null this._selectedEntry = null if (this.regionLayer?.layer?.getSource()) { this.regionLayer.layer.getSource().clear() } } reset(farm) { this.clearLayer() this.initData(farm?.id ? [] : []) } } export default RegionLayer