drawRegionMap.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. import * as KMap from "@/utils/ol-map/KMap";
  2. import * as util from "@/common/ol_common.js";
  3. import config from "@/api/config.js";
  4. import Style from "ol/style/Style";
  5. import Icon from "ol/style/Icon";
  6. import { Fill, Stroke, Text } from "ol/style.js";
  7. import { Point } from 'ol/geom';
  8. import Feature from "ol/Feature";
  9. import * as proj from "ol/proj";
  10. import { getArea } from 'ol/sphere.js';
  11. import WKT from "ol/format/WKT.js";
  12. import proj4 from "proj4"
  13. import { register } from "ol/proj/proj4";
  14. proj4.defs("EPSG:38572", "+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +k=1 +units=m +nadgrids=@null +wktext +no_defs +type=crs");
  15. register(proj4);
  16. /**
  17. * @description 地图层对象
  18. */
  19. class DrawRegionMap {
  20. constructor() {
  21. let that = this;
  22. let vectorStyle = new KMap.VectorStyle();
  23. this.vectorStyle = vectorStyle;
  24. // 是否允许编辑(勾画页 true,小弹窗回显页 false)
  25. this.editable = true;
  26. // 位置图标
  27. this.clickPointLayer = new KMap.VectorLayer("clickPointLayer", 9999, {
  28. style: () => {
  29. return new Style({
  30. image: new Icon({
  31. src: require("@/assets/img/home/garden-point.png"),
  32. scale: 0.5,
  33. anchor: [0.5, 1],
  34. }),
  35. });
  36. },
  37. });
  38. // 只读状态区域图层(展示多 polygon,用于“已解决 / 未解决”等状态)
  39. this.staticRegionLayer = new KMap.VectorLayer("staticRegionLayer", 9998, {
  40. style: (f) => {
  41. const status = f.get("status"); // 'resolved' | 'unresolved'
  42. const isResolved = status === "resolved";
  43. // 已解决:深灰填充,浅白描边;未解决:浅蓝填充,亮蓝描边
  44. const fillColor = isResolved ? "rgba(0, 0, 0, 0.6)" : "rgba(0, 0, 0, 0.5)";
  45. const strokeColor = isResolved ? "#7C7C7C" : "#2199F8";
  46. const text = new Text({
  47. text: status === "resolved" ? "已解决" : "未解决",
  48. font: "12px sans-serif",
  49. fill: new Fill({ color: "#ffffff" }),
  50. backgroundFill: new Fill({
  51. color: isResolved ? "#949494" : "#2199F8"
  52. }),
  53. padding: [1, 5, 1, 5],
  54. });
  55. const style = new Style({
  56. fill: new Fill({ color: fillColor }),
  57. stroke: new Stroke({ color: strokeColor, width: 1 }),
  58. text: text,
  59. })
  60. const text2 = new Style({
  61. text: new Text({
  62. text: `发现时间:${f.get("updatedTime")}`,
  63. font: "12px sans-serif",
  64. offsetY: -24,
  65. fill: new Fill({ color: "#ffffff" }),
  66. backgroundFill: new Fill({
  67. color: isResolved ? "rgba(171, 171, 171, 0.4)" : "rgba(33, 153, 248, 0.6)"
  68. }),
  69. padding: [1, 5, 1, 5],
  70. }),
  71. });
  72. return [style, text2];
  73. },
  74. });
  75. }
  76. /**
  77. * 初始化地图
  78. * @param {string} location WKT 点位
  79. * @param {HTMLElement|string} target 地图容器
  80. * @param {boolean} editable 是否允许绘制/编辑地块
  81. * @param {boolean} movable 是否允许拖动/缩放地图
  82. * @param {boolean} showPoint 是否显示初始点位图标
  83. */
  84. initMap(location, target, editable = true, movable = true, showPoint = true) {
  85. let level = 16;
  86. let coordinate = util.wktCastGeom(location).getFirstCoordinate();
  87. this.kmap = new KMap.Map(target, level, coordinate[0], coordinate[1], null, 8, 22);
  88. // 记录当前地图是否可编辑,供样式控制使用
  89. this.editable = editable;
  90. let xyz2 = config.base_img_url3 + "map/lby/{z}/{x}/{y}.png";
  91. this.kmap.addXYZLayer(xyz2, { minZoom: 8, maxZoom: 22 }, 2);
  92. this.kmap.addLayer(this.clickPointLayer.layer);
  93. this.kmap.addLayer(this.staticRegionLayer.layer);
  94. // 根据 showPoint 决定是否在初始化时落下点位图标
  95. if (showPoint) {
  96. this.setMapPoint(coordinate);
  97. } else {
  98. this.clickPointLayer.source.clear();
  99. }
  100. // 仅在 editable 为 true 时开启绘制/编辑能力(用于勾画页面)
  101. if (editable) {
  102. this.kmap.initDraw((e) => {
  103. // drawend事件:绘制结束后的处理(支持绘制多个地块)
  104. })
  105. this.kmap.startDraw()
  106. this.kmap.modifyDraw()
  107. }
  108. // movable 为 false 时,禁用地图拖动、缩放等交互(用于小弹窗只看不动)
  109. if (!movable && this.kmap && this.kmap.setStates) {
  110. this.kmap.setStates({
  111. DoubleClickZoom: false,
  112. DragAndDrop: false,
  113. MouseWheelZoom: false,
  114. });
  115. }
  116. }
  117. /**
  118. * 回显地块
  119. * @param {string[]} geometryArr 多边形 WKT 数组
  120. * @param {boolean} needFitView 是否自动缩放视图
  121. * @param {string|number} areaText 显示的面积(单位:亩),可选
  122. */
  123. setAreaGeometry(geometryArr, needFitView = false, areaText) {
  124. // 兜底保护:geometryArr 可能为 undefined/null 或空数组
  125. if (!Array.isArray(geometryArr) || geometryArr.length === 0) return;
  126. // 地图实例或图层尚未初始化时也直接返回,避免报错
  127. if (!this.kmap || !this.kmap.polygonLayer || !this.kmap.polygonLayer.source) return;
  128. let that = this;
  129. geometryArr.map(item => {
  130. // 不使用 setLayerWkt,而是手动添加要素,避免自动缩放视图
  131. const format = new WKT()
  132. const mapProjection = that.kmap.map.getView().getProjection()
  133. let geometry = format.readGeometry(item, {
  134. dataProjection: 'EPSG:4326',
  135. featureProjection: mapProjection
  136. })
  137. let f = new Feature({ geometry: geometry })
  138. // 只读模式下,为多边形单独设置样式:仅填充+边框 + 面积文本,不显示可拖动的顶点小圆点
  139. if (!this.editable) {
  140. const styles = [
  141. new Style({
  142. fill: new Fill({
  143. color: "rgba(0, 0, 0, 0.5)",
  144. }),
  145. stroke: new Stroke({
  146. color: "#2199F8",
  147. width: 2,
  148. }),
  149. }),
  150. ];
  151. // 面积文本(如:24亩),优先使用传入的 areaText
  152. const textValue = areaText != null && areaText !== ""
  153. ? `${areaText}亩`
  154. : "";
  155. if (textValue) {
  156. styles.push(
  157. new Style({
  158. text: new Text({
  159. text: textValue,
  160. font: "15px sans-serif",
  161. fill: new Fill({ color: "#ffffff" }),
  162. }),
  163. })
  164. );
  165. }
  166. f.setStyle(styles);
  167. }
  168. that.kmap.polygonLayer.source.addFeature(f)
  169. })
  170. // 根据参数决定是否需要自适应地块范围
  171. if (needFitView) {
  172. this.fitView()
  173. }
  174. }
  175. fitView() {
  176. let extent = this.kmap.polygonLayer.source.getExtent()
  177. // 地图自适应到区域可视范围
  178. this.kmap.getView().fit(extent, { duration: 500, padding: [100, 100, 100, 100] });
  179. }
  180. clearLayer() {
  181. // this.kmap.removeLayer(this.clickPointLayer.layer)
  182. if (this.kmap && this.kmap.polygonLayer) {
  183. this.kmap.polygonLayer.source.clear();
  184. }
  185. if (this.staticRegionLayer && this.staticRegionLayer.source) {
  186. this.staticRegionLayer.source.clear();
  187. }
  188. }
  189. /**
  190. * 销毁地图实例(用于从编辑态切换到仅查看时重新初始化)
  191. */
  192. destroyMap() {
  193. this.clearLayer();
  194. if (this.kmap && typeof this.kmap.destroy === "function") {
  195. this.kmap.destroy();
  196. }
  197. this.kmap = null;
  198. }
  199. getAreaGeometry() {
  200. const features = this.kmap.getLayerFeatures()
  201. let geometryArr = []
  202. let area = 0
  203. const format = new WKT()
  204. // 获取图层上的Polygon,转成WKT用于回显
  205. features.forEach(item => {
  206. // 使用 writeGeometry 而不是 writeFeature,因为 setLayerWkt 期望的是几何体的 WKT
  207. const geometry = item.getGeometry()
  208. geometryArr.push(format.writeGeometry(geometry, {
  209. dataProjection: 'EPSG:4326',
  210. featureProjection: this.kmap.map.getView().getProjection()
  211. }))
  212. let geom = geometry.clone()
  213. geom.transform(proj.get("EPSG:4326"), proj.get("EPSG:38572"))
  214. let areaItem = getArea(geom)
  215. areaItem = (areaItem + areaItem / 2) / 1000;
  216. area += areaItem
  217. })
  218. return { geometryArr, mianji: area.toFixed(2) } // 修改为 mianji 字段,与创建页面保持一致
  219. }
  220. setMapPosition(center) {
  221. this.kmap.getView().animate({
  222. center,
  223. zoom: 17,
  224. duration: 500,
  225. });
  226. this.setMapPoint(center)
  227. }
  228. setMapPoint(coordinate) {
  229. this.clickPointLayer.source.clear()
  230. let point = new Feature(new Point(coordinate))
  231. this.clickPointLayer.addFeature(point)
  232. }
  233. // 删除当前地块(删除最新绘制的一个地块)
  234. deleteCurrentPolygon() {
  235. if (!this.kmap || !this.kmap.polygonLayer) return;
  236. const features = this.kmap.polygonLayer.source.getFeatures();
  237. if (features && features.length > 0) {
  238. const lastFeature = features[features.length - 1];
  239. this.kmap.polygonLayer.source.removeFeature(lastFeature);
  240. }
  241. }
  242. /**
  243. * 设置只读状态区域图层(多个 polygon,不可编辑)
  244. * @param {Array<{ geometry: string, status?: 'resolved' | 'unresolved' }>} regions
  245. *
  246. * 使用示例:
  247. * drawRegionMap.setStatusRegions([
  248. * { geometry: 'MULTIPOLYGON(((...)))', status: 'resolved' },
  249. * { geometry: 'MULTIPOLYGON(((...)))', status: 'unresolved' },
  250. * ]);
  251. */
  252. setStatusRegions(regions) {
  253. if (!this.kmap || !this.staticRegionLayer || !this.staticRegionLayer.source) return;
  254. // 仅操作只读图层,不影响当前地图的绘制 / 编辑状态
  255. this.staticRegionLayer.source.clear();
  256. if (!Array.isArray(regions) || regions.length === 0) return;
  257. const format = new WKT();
  258. const mapProjection = this.kmap.map.getView().getProjection();
  259. regions.forEach((region) => {
  260. if (!region || !region.geometry) return;
  261. try {
  262. const geometry = format.readGeometry(region.geometry, {
  263. dataProjection: "EPSG:4326",
  264. featureProjection: mapProjection,
  265. });
  266. const feature = new Feature({ geometry });
  267. feature.set("status", region.status || "unresolved");
  268. feature.set("updatedTime", region.updatedTime);
  269. this.staticRegionLayer.addFeature(feature);
  270. } catch (e) {
  271. // 单个区域解析失败时忽略
  272. }
  273. });
  274. // 不在这里直接缩放,避免只按某一层适配视图;
  275. // 由调用方在需要时统一调用 fitAllRegions 进行视图自适应
  276. }
  277. /**
  278. * 视图自适应到「只读区域图层 + 可编辑多边形图层」的联合范围
  279. * 适用于同时存在接口返回区域和本地勾画区域时,保证都能出现在视野内
  280. */
  281. fitAllRegions() {
  282. if (!this.kmap) return;
  283. const extents = [];
  284. // 只读状态区域图层范围
  285. if (this.staticRegionLayer && this.staticRegionLayer.source) {
  286. const features = this.staticRegionLayer.source.getFeatures();
  287. if (features && features.length > 0) {
  288. extents.push(this.staticRegionLayer.source.getExtent());
  289. }
  290. }
  291. // 可编辑 polygon 图层范围
  292. if (this.kmap.polygonLayer && this.kmap.polygonLayer.source) {
  293. const features = this.kmap.polygonLayer.source.getFeatures();
  294. if (features && features.length > 0) {
  295. extents.push(this.kmap.polygonLayer.source.getExtent());
  296. }
  297. }
  298. if (extents.length === 0) return;
  299. // 计算所有范围的并集 [minX, minY, maxX, maxY]
  300. const merged = extents.reduce(
  301. (acc, cur) => {
  302. if (!acc) return cur.slice();
  303. return [
  304. Math.min(acc[0], cur[0]),
  305. Math.min(acc[1], cur[1]),
  306. Math.max(acc[2], cur[2]),
  307. Math.max(acc[3], cur[3]),
  308. ];
  309. },
  310. null
  311. );
  312. if (merged) {
  313. this.kmap.getView().fit(merged, { duration: 500, padding: [100, 100, 100, 100] });
  314. }
  315. }
  316. }
  317. export default DrawRegionMap;