| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475 |
- import * as KMap from "@/utils/ol-map/KMap";
- import * as util from "@/common/ol_common.js";
- import config from "@/api/config.js";
- import Style from "ol/style/Style";
- import Icon from "ol/style/Icon";
- import { Fill, Stroke, Text } from "ol/style.js";
- import { Point } from 'ol/geom';
- import Feature from "ol/Feature";
- import * as proj from "ol/proj";
- import { getArea } from 'ol/sphere.js';
- import WKT from "ol/format/WKT.js";
- import proj4 from "proj4"
- import { register } from "ol/proj/proj4";
- 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");
- register(proj4);
- /**
- * @description 地图层对象
- */
- class DrawRegionMap {
- constructor() {
- let that = this;
- let vectorStyle = new KMap.VectorStyle();
- this.vectorStyle = vectorStyle;
- // 是否允许编辑(勾画页 true,小弹窗回显页 false)
- this.editable = true;
- // 位置图标
- this.clickPointLayer = new KMap.VectorLayer("clickPointLayer", 9999, {
- style: () => {
- return new Style({
- image: new Icon({
- // src: require("@/assets/img/home/garden-point.png"),
- src: require("@/assets/img/map/map_point.png"),
- scale: 0.5,
- // anchor: [0.5, 0.5],
- }),
- });
- },
- });
- // 只读状态区域图层(展示多 polygon,用于“已解决 / 未解决”等状态)
- // 层级需低于默认可编辑 polygonLayer(1000),避免标签遮挡当前编辑地块
- this.staticRegionLayer = new KMap.VectorLayer("staticRegionLayer", 900, {
- style: (f) => {
- const displayMode = f.get("displayMode");
- // 品种勾画页:已勾画的其他品种仅做只读灰色展示,并显示品种名
- if (displayMode === "readonlyVariety") {
- // 品种区只读展示:统一使用品种区视觉样式
- return new Style({
- fill: new Fill({ color: "rgba(0, 57, 44, 0.5)" }),
- stroke: new Stroke({ color: "#18AA8B", width: 1.5 }),
- text: new Text({
- text: f.get("label") || "",
- font: "12px sans-serif",
- fill: new Fill({ color: "#ffffff" }),
- backgroundFill: new Fill({ color: "rgba(0, 57, 44, 0.85)" }),
- padding: [2, 6, 2, 6],
- }),
- });
- }
- const status = f.get("status"); // 'resolved' | 'unresolved'
- const reproductiveName = f.get("reproductiveName") || "";
- const isResolved = status === "resolved";
- const unresolvedBlueFill = f.get("unresolvedBlueFill") === true;
- // 已解决:深灰填充,浅白描边;未解决:默认深灰半透明 + 蓝描边;unresolvedBlueFill 时浅蓝填充(互动列表等)
- const fillColor = isResolved
- ? "rgba(0, 0, 0, 0.6)"
- : unresolvedBlueFill
- ? "rgba(33, 153, 248, 0.35)"
- : "rgba(0, 0, 0, 0.5)";
- const strokeColor = isResolved ? "#7C7C7C" : "#2199F8";
- const text = new Text({
- text: reproductiveName ? `${reproductiveName} ${status === "resolved" ? "已解决" : "未解决"}` : status === "resolved" ? "已解决" : "未解决",
- font: "12px sans-serif",
- fill: new Fill({ color: "#ffffff" }),
- backgroundFill: new Fill({
- color: isResolved ? "#949494" : "#2199F8"
- }),
- padding: [1, 5, 1, 5],
- });
- const style = new Style({
- fill: new Fill({ color: fillColor }),
- stroke: new Stroke({ color: strokeColor, width: 1 }),
- text: text,
- })
- const text2 = new Style({
- text: new Text({
- text: `发现时间:${f.get("updatedTime")}`,
- font: "12px sans-serif",
- offsetY: -24,
- fill: new Fill({ color: "#ffffff" }),
- backgroundFill: new Fill({
- color: isResolved ? "rgba(171, 171, 171, 0.4)" : "rgba(33, 153, 248, 0.6)"
- }),
- padding: [1, 5, 1, 5],
- }),
- });
- return [style, text2];
- },
- });
- }
- /**
- * 初始化地图
- * @param {string} location WKT 点位
- * @param {HTMLElement|string} target 地图容器
- * @param {boolean} editable 是否允许绘制/编辑地块
- * @param {boolean} movable 是否允许拖动/缩放地图
- * @param {boolean} showPoint 是否显示初始点位图标
- * @param {Function} onDrawEnd 绘制闭环回调(drawend),可选
- */
- initMap(location, target, editable = true, movable = true, showPoint = true, onDrawEnd) {
- let level = 16;
- let coordinate = util.wktCastGeom(location).getFirstCoordinate();
- this.kmap = new KMap.Map(target, level, coordinate[0], coordinate[1], null, 8, 22);
- // 记录当前地图是否可编辑,供样式控制使用
- this.editable = editable;
- 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.clickPointLayer.layer);
- this.kmap.addLayer(this.staticRegionLayer.layer);
- // 根据 showPoint 决定是否在初始化时落下点位图标
- if (showPoint) {
- this.setMapPoint(coordinate);
- } else {
- this.clickPointLayer.source.clear();
- }
- // 仅在 editable 为 true 时开启绘制/编辑能力(用于勾画页面)
- if (editable) {
- this.kmap.initDraw((e) => {
- if (typeof onDrawEnd === "function") {
- try {
- onDrawEnd(e);
- } catch (_) {
- // 回调失败不影响地图继续使用
- }
- }
- });
- this.kmap.startDraw()
- this.kmap.modifyDraw()
- }
- // movable 为 false 时,禁用地图拖动、缩放等交互(用于小弹窗只看不动)
- if (!movable && this.kmap && this.kmap.setStates) {
- this.kmap.setStates({
- DoubleClickZoom: false,
- DragAndDrop: false,
- MouseWheelZoom: false,
- });
- }
- }
- /**
- * 回显地块
- * @param {string[]} geometryArr 多边形 WKT 数组
- * @param {boolean} needFitView 是否自动缩放视图
- * @param {string|number} areaText 显示的面积(单位:亩),可选
- * @param {{ fill?: string, stroke?: string }} [readonlyAreaStyle] 只读模式下覆盖填充/描边色;不传则仍用 Map.drawStyleColors 或默认
- * @param {{ badgeText: string, discoveryDate: string, badgeBackground?: string }} [growthOverlay] 只读模式下异常区标签(配色与勾画页一致)
- * @param {string} [polygonCenterLabel] 只读模式下覆盖多边形中心文案(原样展示,不加「亩」);用于品种查看态显示品种名等
- */
- setAreaGeometry(geometryArr, needFitView = false, areaText, readonlyAreaStyle, growthOverlay, polygonCenterLabel) {
- // 兜底保护:geometryArr 可能为 undefined/null 或空数组
- if (!Array.isArray(geometryArr) || geometryArr.length === 0) return;
- // 地图实例或图层尚未初始化时也直接返回,避免报错
- if (!this.kmap || !this.kmap.polygonLayer || !this.kmap.polygonLayer.source) return;
- let that = this;
- geometryArr.map(item => {
- // 不使用 setLayerWkt,而是手动添加要素,避免自动缩放视图
- const format = new WKT()
- const mapProjection = that.kmap.map.getView().getProjection()
- let geometry = format.readGeometry(item, {
- dataProjection: 'EPSG:4326',
- featureProjection: mapProjection
- })
- let f = new Feature({ geometry: geometry })
- // 只读模式下,为多边形单独设置样式:仅填充+边框 + 面积文本,不显示可拖动的顶点小圆点
- if (!this.editable) {
- // 查看模式下单块区域展示:优先 readonlyAreaStyle,其次 Map.drawStyleColors,再默认
- const fillColor =
- readonlyAreaStyle?.fill ??
- KMap.Map?.drawStyleColors?.fill ??
- "rgba(0, 57, 44, 0.5)";
- const strokeColor =
- readonlyAreaStyle?.stroke ??
- KMap.Map?.drawStyleColors?.stroke ??
- "#18AA8B";
- const styles = [
- new Style({
- fill: new Fill({
- color: fillColor,
- }),
- stroke: new Stroke({
- color: strokeColor,
- width: 2,
- }),
- }),
- ];
- // 中心文本:优先 polygonCenterLabel(品种查看态显示名称);否则按面积规则
- const trimmedCenterLabel =
- typeof polygonCenterLabel === "string" && polygonCenterLabel.trim() !== ""
- ? polygonCenterLabel.trim()
- : "";
- // 面积文本显示规则:
- // 1) 传空字符串:不显示
- // 2) 传了值:优先显示传入值
- // 3) 未传:按当前地块实时计算亩数
- const isExplicitEmptyText = typeof areaText === "string" && areaText.trim() === "";
- const hasProvidedAreaText = areaText !== undefined && areaText !== null;
- let textValue = "";
- if (trimmedCenterLabel) {
- textValue = trimmedCenterLabel;
- } else if (!isExplicitEmptyText) {
- if (hasProvidedAreaText) {
- textValue = `${areaText}亩`;
- } else {
- try {
- let geom = geometry.clone();
- geom.transform(proj.get("EPSG:4326"), proj.get("EPSG:38572"));
- let areaItem = getArea(geom);
- areaItem = (areaItem + areaItem / 2) / 1000;
- textValue = `${Number(areaItem).toFixed(2)}亩`;
- } catch (_) {
- textValue = "";
- }
- }
- }
- const hasGrowth =
- growthOverlay &&
- growthOverlay.badgeText &&
- growthOverlay.discoveryDate;
- if (textValue) {
- // 品种名与 staticRegionLayer.readonlyVariety 标签视觉一致(12px + 深绿底)
- const isVarietyNameLabel = !!trimmedCenterLabel;
- styles.push(
- new Style({
- text: new Text({
- text: textValue,
- font: isVarietyNameLabel ? "12px sans-serif" : "15px sans-serif",
- fill: new Fill({ color: "#ffffff" }),
- ...(isVarietyNameLabel
- ? {
- backgroundFill: new Fill({
- color: "rgba(0, 57, 44, 0.85)",
- }),
- padding: [2, 6, 2, 6],
- }
- : {}),
- offsetY: hasGrowth ? 14 : 0,
- }),
- })
- );
- }
- if (hasGrowth) {
- styles.push(
- new Style({
- text: new Text({
- text: growthOverlay.badgeText,
- font: "bold 13px sans-serif",
- fill: new Fill({ color: "#ffffff" }),
- backgroundFill: new Fill({
- color: growthOverlay.badgeBackground || "#FF7F00",
- }),
- padding: [4, 10, 4, 10],
- offsetY: -40,
- }),
- }),
- new Style({
- text: new Text({
- text: `发现时间:${growthOverlay.discoveryDate}`,
- font: "12px sans-serif",
- fill: new Fill({ color: "#ffffff" }),
- offsetY: -16,
- }),
- })
- );
- }
- f.setStyle(styles);
- }
- that.kmap.polygonLayer.source.addFeature(f)
- })
- // 根据参数决定是否需要自适应地块范围
- if (needFitView) {
- this.fitView()
- }
- }
- fitView() {
- if (!this.kmap?.polygonLayer?.source) return;
- const extent = this.kmap.polygonLayer.source.getExtent();
- if (
- !extent ||
- !isFinite(extent[0]) ||
- !isFinite(extent[1]) ||
- !isFinite(extent[2]) ||
- !isFinite(extent[3])
- ) {
- return;
- }
- this.kmap.getView().fit(extent, { duration: 500, padding: [10, 10, 10, 10] });
- }
- clearLayer() {
- // this.kmap.removeLayer(this.clickPointLayer.layer)
- if (this.kmap && this.kmap.polygonLayer) {
- this.kmap.polygonLayer.source.clear();
- }
- if (this.staticRegionLayer && this.staticRegionLayer.source) {
- this.staticRegionLayer.source.clear();
- }
- }
- /** 取消当前未完成的勾画(草图在 Draw 的 overlay 上,仅 clear 多边形图层去不掉) */
- abortOngoingDrawSketch() {
- if (!this.kmap || !this.kmap.draw || typeof this.kmap.draw.abortDrawing !== "function") return;
- this.kmap.draw.abortDrawing();
- }
- /**
- * 销毁地图实例(用于从编辑态切换到仅查看时重新初始化)
- */
- destroyMap() {
- this.clearLayer();
- if (this.kmap && typeof this.kmap.destroy === "function") {
- this.kmap.destroy();
- }
- this.kmap = null;
- }
- getAreaGeometry() {
- const features = this.kmap.getLayerFeatures()
- console.log(features, 'features');
- let geometryArr = []
- let area = 0
- const format = new WKT()
- // 获取图层上的Polygon,转成WKT用于回显
- features.forEach(item => {
- console.log(item, 'item');
- // 使用 writeGeometry 而不是 writeFeature,因为 setLayerWkt 期望的是几何体的 WKT
- const geometry = item.getGeometry()
- geometryArr.push(format.writeGeometry(geometry, {
- dataProjection: 'EPSG:4326',
- featureProjection: this.kmap.map.getView().getProjection()
- }))
- let geom = geometry.clone()
- geom.transform(proj.get("EPSG:4326"), proj.get("EPSG:38572"))
- let areaItem = getArea(geom)
- areaItem = (areaItem + areaItem / 2) / 1000;
- area += areaItem
- })
- return { geometryArr, mianji: area.toFixed(2) } // 修改为 mianji 字段,与创建页面保持一致
- }
- setMapPosition(center) {
- this.kmap.getView().animate({
- center,
- zoom: 17,
- duration: 500,
- });
- this.setMapPoint(center)
- }
- setMapPoint(coordinate) {
- this.clickPointLayer.source.clear()
- let point = new Feature(new Point(coordinate))
- this.clickPointLayer.addFeature(point)
- }
- // 删除当前地块(删除最新绘制的一个地块)
- deleteCurrentPolygon() {
- if (!this.kmap || !this.kmap.polygonLayer) return;
- const features = this.kmap.polygonLayer.source.getFeatures();
- if (features && features.length > 0) {
- const lastFeature = features[features.length - 1];
- this.kmap.polygonLayer.source.removeFeature(lastFeature);
- }
- }
- /**
- * 设置只读状态区域图层(多个 polygon,不可编辑)
- * @param {Array<{ geometry: string, status?: 'resolved' | 'unresolved', label?: string, displayMode?: string, unresolvedBlueFill?: boolean }>} regions
- *
- * 使用示例:
- * drawRegionMap.setStatusRegions([
- * { geometry: 'MULTIPOLYGON(((...)))', status: 'resolved' },
- * { geometry: 'MULTIPOLYGON(((...)))', status: 'unresolved' },
- * ]);
- */
- setStatusRegions(regions) {
- if (!this.kmap || !this.staticRegionLayer || !this.staticRegionLayer.source) return;
- // 仅操作只读图层,不影响当前地图的绘制 / 编辑状态
- this.staticRegionLayer.source.clear();
- if (!Array.isArray(regions) || regions.length === 0) return;
- const format = new WKT();
- const mapProjection = this.kmap.map.getView().getProjection();
- regions.forEach((region) => {
- if (!region || !region.geometry) return;
- try {
- const geometry = format.readGeometry(region.geometry, {
- dataProjection: "EPSG:4326",
- featureProjection: mapProjection,
- });
- const feature = new Feature({ geometry });
- feature.set("reproductiveName", region.reproductiveName);
- feature.set("status", region.status || "unresolved");
- feature.set("updatedTime", region.updatedTime);
- feature.set("label", region.label || "");
- feature.set("displayMode", region.displayMode || "");
- feature.set("unresolvedBlueFill", region.unresolvedBlueFill === true);
- this.staticRegionLayer.addFeature(feature);
- } catch (e) {
- // 单个区域解析失败时忽略
- }
- });
- }
- /**
- * 视图自适应到「只读区域图层 + 可编辑多边形图层」的联合范围
- * 适用于同时存在接口返回区域和本地勾画区域时,保证都能出现在视野内
- */
- fitAllRegions() {
- if (!this.kmap) return;
- const extents = [];
- // 只读状态区域图层范围
- if (this.staticRegionLayer && this.staticRegionLayer.source) {
- const features = this.staticRegionLayer.source.getFeatures();
- if (features && features.length > 0) {
- extents.push(this.staticRegionLayer.source.getExtent());
- }
- }
- // 可编辑 polygon 图层范围
- if (this.kmap.polygonLayer && this.kmap.polygonLayer.source) {
- const features = this.kmap.polygonLayer.source.getFeatures();
- if (features && features.length > 0) {
- extents.push(this.kmap.polygonLayer.source.getExtent());
- }
- }
- if (extents.length === 0) return;
- // 计算所有范围的并集 [minX, minY, maxX, maxY]
- const merged = extents.reduce(
- (acc, cur) => {
- if (!acc) return cur.slice();
- return [
- Math.min(acc[0], cur[0]),
- Math.min(acc[1], cur[1]),
- Math.max(acc[2], cur[2]),
- Math.max(acc[3], cur[3]),
- ];
- },
- null
- );
- if (merged) {
- this.kmap.getView().fit(merged, { duration: 500, padding: [100, 100, 100, 100] });
- }
- }
- }
- export default DrawRegionMap;
|