regionLayer.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. import * as KMap from '@/utils/ol-map/KMap.js'
  2. import * as util from '@/common/ol_common.js'
  3. import VectorLayer from 'ol/layer/Vector.js'
  4. import { Vector as VectorSource } from 'ol/source'
  5. import { newRegionFeature } from '@/utils/map.js'
  6. import Style from 'ol/style/Style'
  7. import Fill from 'ol/style/Fill'
  8. import Stroke from 'ol/style/Stroke'
  9. import Overlay from 'ol/Overlay'
  10. import { getCenter } from 'ol/extent'
  11. import { createRegionInfoCard, updateRegionInfoCard } from './regionInfoCard.js'
  12. const FILL_DEFAULT = 'rgba(0, 0, 0, 0.06)'
  13. const FILL_SELECTED = 'rgba(255, 166, 23, 0.25)'
  14. const STROKE_DEFAULT = '#ffffff'
  15. const STROKE_SELECTED = '#FFA617'
  16. /**
  17. * 地图分区图层 + 分区信息框
  18. */
  19. class RegionLayer {
  20. constructor(kmap, options = {}) {
  21. const that = this
  22. this.kmap = kmap
  23. this.enterSelectTree = !!options.enterSelectTree
  24. this.onRegionSelect = options.onRegionSelect
  25. this.selectedRegionId = null
  26. this.regionOverlays = []
  27. this._regionClickHandler = null
  28. this._selectedEntry = null
  29. this._styleCache = {
  30. normal: new Style({
  31. fill: new Fill({ color: FILL_DEFAULT }),
  32. stroke: new Stroke({ color: STROKE_DEFAULT, width: 1 }),
  33. }),
  34. selected: new Style({
  35. fill: new Fill({ color: FILL_SELECTED }),
  36. stroke: new Stroke({ color: STROKE_SELECTED, width: 2 }),
  37. }),
  38. }
  39. this.regionLayer = new KMap.VectorLayer('regionLayer', 99, {
  40. minZoom: 14.6,
  41. renderMode: 'image',
  42. updateWhileAnimating: false,
  43. updateWhileInteracting: false,
  44. style: (f) => that.getRegionStyle(f),
  45. })
  46. kmap.addLayer(this.regionLayer.layer)
  47. }
  48. get map() {
  49. return this.kmap.map
  50. }
  51. getRegionStyle(feature) {
  52. return feature.get('selected')
  53. ? [this._styleCache.selected]
  54. : [this._styleCache.normal]
  55. }
  56. getFeatureCenter(feature, item) {
  57. if (item?.pointWkt) {
  58. try {
  59. return util.wktCastGeom(item.pointWkt).getFirstCoordinate()
  60. } catch {
  61. /* ignore */
  62. }
  63. }
  64. const geom = feature.getGeometry()
  65. if (!geom) return null
  66. const type = geom.getType()
  67. if (type === 'Polygon' && typeof geom.getInteriorPoint === 'function') {
  68. return geom.getInteriorPoint().getCoordinates()
  69. }
  70. if (type === 'MultiPolygon' && typeof geom.getInteriorPoints === 'function') {
  71. const points = geom.getInteriorPoints()
  72. return points.getPoint(0).getCoordinates()
  73. }
  74. return getCenter(geom.getExtent())
  75. }
  76. clearRegionInfoOverlays() {
  77. this.regionOverlays.forEach(({ overlay }) => {
  78. this.map.removeOverlay(overlay)
  79. })
  80. this.regionOverlays = []
  81. }
  82. clearRegionClick() {
  83. if (this._regionClickHandler) {
  84. this.kmap.off('singleclick', this._regionClickHandler)
  85. this._regionClickHandler = null
  86. }
  87. }
  88. bindRegionClick() {
  89. this.clearRegionClick()
  90. const that = this
  91. this._regionClickHandler = (evt) => {
  92. if (!that.enterSelectTree) return
  93. let hit = null
  94. that.map.forEachFeatureAtPixel(
  95. evt.pixel,
  96. (feature, layer) => {
  97. if (layer !== that.regionLayer.layer) return
  98. const entry = that.regionOverlays.find((r) => r.feature === feature)
  99. if (entry) hit = entry
  100. },
  101. {
  102. layerFilter: (layer) => layer === that.regionLayer.layer,
  103. hitTolerance: 5,
  104. },
  105. )
  106. if (hit) that.selectRegion(hit)
  107. }
  108. this.kmap.on('singleclick', this._regionClickHandler)
  109. }
  110. selectRegion(entry) {
  111. if (!this.enterSelectTree || !entry) return
  112. if (this._selectedEntry === entry) return
  113. const prev = this._selectedEntry
  114. if (prev) {
  115. prev.feature.set('selected', false)
  116. updateRegionInfoCard(prev.el, {
  117. enterSelectTree: true,
  118. selected: false,
  119. })
  120. }
  121. entry.feature.set('selected', true)
  122. this._selectedEntry = entry
  123. this.selectedRegionId = entry.item.id ?? entry.item.regionId ?? entry.feature.getId()
  124. updateRegionInfoCard(entry.el, {
  125. enterSelectTree: true,
  126. selected: true,
  127. })
  128. // 合并为一次图层刷新,减轻 Canvas 矢量层整层重绘闪烁
  129. const source = this.regionLayer.layer.getSource()
  130. if (source) {
  131. source.changed()
  132. }
  133. if (typeof this.onRegionSelect === 'function') {
  134. this.onRegionSelect(entry.item, entry.feature)
  135. }
  136. }
  137. addRegionInfoOverlay(item, feature) {
  138. const coord = this.getFeatureCenter(feature, item)
  139. if (!coord) return null
  140. const entry = { item, feature, el: null, overlay: null }
  141. const el = createRegionInfoCard(item, {
  142. enterSelectTree: this.enterSelectTree,
  143. selected: false,
  144. onClick: () => this.selectRegion(entry),
  145. })
  146. entry.el = el
  147. const overlay = new Overlay({
  148. element: el,
  149. position: coord,
  150. positioning: 'bottom-center',
  151. stopEvent: this.enterSelectTree,
  152. offset: [0, -4],
  153. })
  154. entry.overlay = overlay
  155. this.map.addOverlay(overlay)
  156. if (this.enterSelectTree) this.bindCardClick(entry)
  157. return entry
  158. }
  159. bindCardClick(entry) {
  160. if (!entry.el || entry.el._regionSelectBound) return
  161. entry.el.addEventListener('click', (e) => {
  162. e.stopPropagation()
  163. if (this.enterSelectTree) this.selectRegion(entry)
  164. })
  165. entry.el._regionSelectBound = true
  166. }
  167. setEnterSelectTree(val) {
  168. this.enterSelectTree = !!val
  169. this.regionOverlays.forEach((r) => {
  170. updateRegionInfoCard(r.el, {
  171. enterSelectTree: this.enterSelectTree,
  172. selected: !!r.feature.get('selected'),
  173. })
  174. if (r.el) {
  175. r.el.style.pointerEvents = this.enterSelectTree ? 'auto' : 'none'
  176. if (this.enterSelectTree) this.bindCardClick(r)
  177. }
  178. if (r.overlay?.setStopEvent) {
  179. r.overlay.setStopEvent(this.enterSelectTree)
  180. }
  181. })
  182. if (this.enterSelectTree) {
  183. this.bindRegionClick()
  184. } else {
  185. this.clearRegionClick()
  186. this.regionOverlays.forEach((r) => {
  187. r.feature.set('selected', false)
  188. updateRegionInfoCard(r.el, { enterSelectTree: false, selected: false })
  189. })
  190. this.selectedRegionId = null
  191. this._selectedEntry = null
  192. this.regionLayer.layer.getSource()?.changed()
  193. }
  194. }
  195. initData(data) {
  196. this.clearLayer()
  197. if (!data || !data.length) return
  198. const features = []
  199. for (const item of data) {
  200. item.regionWkt = item.regionWkt || item.wkt
  201. if (!item.regionWkt) {
  202. console.warn('[regionLayer] 分区缺少 wkt', item)
  203. continue
  204. }
  205. try {
  206. const f = newRegionFeature({
  207. ...item,
  208. bgName: 'defalut',
  209. bgColor: 'defalut',
  210. selected: false,
  211. })
  212. features.push(f)
  213. const entry = this.addRegionInfoOverlay(item, f)
  214. if (entry) {
  215. this.regionOverlays.push(entry)
  216. }
  217. } catch (err) {
  218. console.warn('[regionLayer] 解析分区失败', item, err)
  219. }
  220. }
  221. this.area = features
  222. const source = new VectorSource({ features })
  223. this.regionLayer.layer.setSource(source)
  224. if (features.length) {
  225. const extent = source.getExtent()
  226. if (extent.every((n) => Number.isFinite(n))) {
  227. this.kmap.getView().fit(extent, {
  228. padding: [60, 40, 100, 40],
  229. duration: 600,
  230. maxZoom: 19,
  231. })
  232. }
  233. }
  234. if (this.enterSelectTree) {
  235. this.bindRegionClick()
  236. }
  237. }
  238. clearLayer() {
  239. this.clearRegionClick()
  240. this.clearRegionInfoOverlays()
  241. this.selectedRegionId = null
  242. this._selectedEntry = null
  243. if (this.regionLayer?.layer?.getSource()) {
  244. this.regionLayer.layer.getSource().clear()
  245. }
  246. }
  247. reset(farm) {
  248. this.clearLayer()
  249. this.initData(farm?.id ? [] : [])
  250. }
  251. }
  252. export default RegionLayer